{
    "openapi": "3.1.0",
    "info": {
        "title": "kids-ontour.de, Pflege-Schnittstelle",
        "version": "1.0.0",
        "summary": "Angebote anlegen, ändern und zurückziehen.",
        "description": "Ein Konto sieht und schreibt genau seine eigenen Angebote. Nichts geht direkt online: jede Zeile läuft durch dieselbe Prüfung wie das Formular. Stand der Beschreibung: 2026-10-31.",
        "contact": {
            "url": "https://www.kids-ontour.de/api.html"
        }
    },
    "servers": [
        {
            "url": "https://www.kids-ontour.de"
        }
    ],
    "security": [
        {
            "bearer": []
        }
    ],
    "components": {
        "securitySchemes": {
            "bearer": {
                "type": "http",
                "scheme": "bearer",
                "description": "Das Token aus POST /api/v1/anmeldung. Gilt eine Stunde."
            }
        },
        "schemas": {
            "Angebot": {
                "type": "object",
                "required": [
                    "category_id",
                    "title",
                    "excerpt",
                    "description",
                    "age_from",
                    "age_to",
                    "postal_code",
                    "city",
                    "state"
                ],
                "properties": {
                    "external_id": {
                        "description": "Eure eigene Objektnummer. Eindeutig je Konto, und der Schlüssel für „PUT /angebote/extern/{kennung}“.",
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 120
                    },
                    "category_id": {
                        "description": "Die Rubrik. Die Kennungen stehen unter „/api/v1/rubriken“.",
                        "type": "integer",
                        "minimum": 1
                    },
                    "title": {
                        "description": "Der Name des Angebots, so wie Familien ihn lesen.",
                        "type": "string",
                        "minLength": 5,
                        "maxLength": 180
                    },
                    "excerpt": {
                        "description": "Ein Anriss für die Kachel in der Liste. Reiner Text.",
                        "type": "string",
                        "minLength": 20,
                        "maxLength": 400
                    },
                    "description": {
                        "description": "Die Beschreibung. Einfaches HTML ist erlaubt und läuft durch unseren Filter; was er nicht kennt, fällt weg.",
                        "type": "string",
                        "minLength": 50,
                        "maxLength": 20000
                    },
                    "is_free": {
                        "description": "Eintritt frei. Setzt ihn, bleiben Preis und Preishinweis leer, auch wenn ihr sie schickt.",
                        "type": "boolean"
                    },
                    "price_from": {
                        "description": "Der Preis ab, in ganzen Euro.",
                        "type": "integer",
                        "minimum": 0,
                        "maximum": 99999
                    },
                    "price_to": {
                        "description": "Der obere Preis. Er darf nicht unter dem unteren liegen.",
                        "type": "integer",
                        "minimum": 0,
                        "maximum": 99999
                    },
                    "price_info": {
                        "description": "Alles zum Preis, was keine Zahl ist: Saisontabelle, Gruppenpreise, was im Preis steckt.",
                        "type": "string",
                        "maxLength": 2000
                    },
                    "age_from": {
                        "description": "Ab welchem Alter es passt. Auch für ein Ferienhaus Pflicht; dann nehmt 0.",
                        "type": "integer",
                        "minimum": 0,
                        "maximum": 18
                    },
                    "age_to": {
                        "description": "Bis zu welchem Alter. Aus beiden entstehen die Zielgruppen.",
                        "type": "integer",
                        "minimum": 0,
                        "maximum": 18
                    },
                    "street": {
                        "description": "Straße und Hausnummer.",
                        "type": "string",
                        "maxLength": 160
                    },
                    "postal_code": {
                        "description": "Im Inland fünf Ziffern, und es muss sie geben: an ihr hängen Koordinate, Landkreis und Bundesland. Im Ausland 2 bis 12 Zeichen.",
                        "type": "string",
                        "minLength": 5,
                        "maxLength": 5
                    },
                    "city": {
                        "description": "Der Ort.",
                        "type": "string",
                        "minLength": 2,
                        "maxLength": 120
                    },
                    "state": {
                        "description": "Eines der sechzehn Bundesländer, ausgeschrieben, oder „no_bdland“ für das Ausland.",
                        "type": "string",
                        "enum": [
                            "Baden-Württemberg",
                            "Bayern",
                            "Berlin",
                            "Brandenburg",
                            "Bremen",
                            "Hamburg",
                            "Hessen",
                            "Mecklenburg-Vorpommern",
                            "Niedersachsen",
                            "Nordrhein-Westfalen",
                            "Rheinland-Pfalz",
                            "Saarland",
                            "Sachsen",
                            "Sachsen-Anhalt",
                            "Schleswig-Holstein",
                            "Thüringen",
                            "no_bdland"
                        ]
                    },
                    "country": {
                        "description": "Nur mit „state=no_bdland“, und dann Pflicht. Zwei Buchstaben nach ISO 3166. Deutschland steht nicht zur Wahl: wer es meint, nimmt ein Bundesland.",
                        "type": "string",
                        "enum": [
                            "AL",
                            "AD",
                            "AU",
                            "BE",
                            "BA",
                            "BG",
                            "DK",
                            "EE",
                            "FI",
                            "FR",
                            "GR",
                            "IE",
                            "IS",
                            "IT",
                            "CA",
                            "HR",
                            "LV",
                            "LI",
                            "LT",
                            "LU",
                            "MT",
                            "MD",
                            "MC",
                            "ME",
                            "NL",
                            "NZ",
                            "MK",
                            "NO",
                            "AT",
                            "PL",
                            "PT",
                            "RO",
                            "SM",
                            "SE",
                            "CH",
                            "RS",
                            "SK",
                            "SI",
                            "ES",
                            "ZA",
                            "CZ",
                            "TR",
                            "HU",
                            "US",
                            "GB",
                            "CY"
                        ]
                    },
                    "phone": {
                        "description": "Die Telefonnummer für Familien.",
                        "type": "string",
                        "maxLength": 60
                    },
                    "email": {
                        "description": "Die Adresse, an die Anfragen gehen. Wir speichern keine Anfrage, wir leiten sie weiter.",
                        "type": "string",
                        "maxLength": 190
                    },
                    "website": {
                        "description": "Eure Webseite, mit oder ohne „https://“.",
                        "type": "string",
                        "maxLength": 255
                    },
                    "facebook_url": {
                        "description": "Die Facebook-Seite.",
                        "type": "string",
                        "maxLength": 255
                    },
                    "instagram_url": {
                        "description": "Die Instagram-Seite.",
                        "type": "string",
                        "maxLength": 255
                    },
                    "video_url": {
                        "description": "Ein YouTube-Verweis, wie er in der Adresszeile steht. Gehört zu Premium.",
                        "type": "string",
                        "maxLength": 255
                    },
                    "opening_hours_text": {
                        "description": "Die Öffnungszeiten als Text.",
                        "type": "string",
                        "maxLength": 2000
                    },
                    "is_year_round": {
                        "description": "Ganzjährig geöffnet.",
                        "type": "boolean"
                    },
                    "runs_from": {
                        "description": "Beginn des Zeitraums, „JJJJ-MM-TT“. Für Camps und Ferienangebote.",
                        "type": "string",
                        "format": "date"
                    },
                    "runs_to": {
                        "description": "Ende des Zeitraums. Nach Ablauf nimmt die Wartung das Angebot heraus.",
                        "type": "string",
                        "format": "date"
                    },
                    "is_accessible": {
                        "description": "Barrierefrei.",
                        "type": "boolean"
                    },
                    "dogs_allowed": {
                        "description": "Hunde erlaubt.",
                        "type": "boolean"
                    },
                    "is_indoor": {
                        "description": "Drinnen. Nur in Rubriken, die das Merkmal führen; sonst fällt der Haken weg.",
                        "type": "boolean"
                    },
                    "is_outdoor": {
                        "description": "Draußen, mit derselben Regel.",
                        "type": "boolean"
                    },
                    "schlagwoerter": {
                        "description": "Als Text, durch Kommas getrennt, höchstens acht. Eigener Weg: „PUT /angebote/{id}/schlagwoerter“.",
                        "type": "string",
                        "maxLength": 400
                    },
                    "id": {
                        "description": "Unsere Kennung des Angebots.",
                        "readOnly": true
                    },
                    "status": {
                        "description": "draft, pending, published, rejected, expired oder archived.",
                        "readOnly": true
                    },
                    "slug": {
                        "description": "Der Adressteil, den wir aus dem Titel bilden.",
                        "readOnly": true
                    },
                    "district": {
                        "description": "Der Landkreis, aus der Postleitzahl.",
                        "readOnly": true
                    },
                    "latitude": {
                        "description": "Breitengrad, aus der Anschrift gerechnet.",
                        "readOnly": true
                    },
                    "longitude": {
                        "description": "Längengrad.",
                        "readOnly": true
                    },
                    "completeness": {
                        "description": "Wie vollständig der Eintrag ist, 0 bis 100.",
                        "readOnly": true
                    },
                    "paused_at": {
                        "description": "Gesetzt, solange das Angebot pausiert.",
                        "readOnly": true
                    },
                    "terms_accepted_at": {
                        "description": "Wann die Bedingungen für dieses Angebot galten.",
                        "readOnly": true
                    },
                    "created_at": {
                        "description": "Angelegt am.",
                        "readOnly": true
                    },
                    "updated_at": {
                        "description": "Zuletzt geändert am.",
                        "readOnly": true
                    },
                    "url": {
                        "description": "Die Adresse im Portal, sobald das Angebot online steht, sonst null.",
                        "readOnly": true
                    }
                }
            },
            "Problem": {
                "type": "object",
                "description": "Ein Fehler nach RFC 9457. `fields` ist unsere Ergänzung: eine Meldung je Feld.",
                "properties": {
                    "type": {
                        "type": "string",
                        "format": "uri"
                    },
                    "title": {
                        "type": "string"
                    },
                    "status": {
                        "type": "integer"
                    },
                    "detail": {
                        "type": "string"
                    },
                    "fields": {
                        "type": "object",
                        "additionalProperties": {
                            "type": "string"
                        }
                    }
                }
            }
        }
    },
    "paths": {
        "/api/v1/anmeldung": {
            "post": {
                "summary": "Zugangsdaten gegen ein Token",
                "description": "Kennung und Geheimnis herein, ein Token hinaus. Es gilt eine Stunde und trägt die Rechte des Schlüssels.",
                "responses": {
                    "200": {
                        "description": "Erledigt.",
                        "content": {
                            "application/json": {
                                "example": {
                                    "access_token": "a1b2c3d4e5f6a7b8c9.4f3e2d1c0b9a8f7e6d5c4b3a2910ff8877665544332211aabbccddeeff0011",
                                    "token_type": "Bearer",
                                    "expires_in": 3600,
                                    "scope": "lesen schreiben"
                                }
                            }
                        }
                    },
                    "400": {
                        "description": "Der Rumpf war kein JSON, oder eine Angabe fehlt, die kein Feld ist.",
                        "content": {
                            "application/problem+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Problem"
                                }
                            }
                        }
                    },
                    "401": {
                        "description": "Kein Token, falsches Token, abgelaufen, widerrufen, oder von einer Adresse, die nicht in der Liste steht.",
                        "content": {
                            "application/problem+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Problem"
                                }
                            }
                        }
                    },
                    "429": {
                        "description": "Zu viele Anfragen. `Retry-After` sagt, wann es weitergeht.",
                        "content": {
                            "application/problem+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Problem"
                                }
                            }
                        }
                    }
                },
                "security": [],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/Angebot"
                            }
                        }
                    }
                }
            }
        },
        "/api/v1/rubriken": {
            "get": {
                "summary": "Der Katalog",
                "description": "Die Rubriken mit ihren Kennungen und den Merkmalen, die sie führen, dazu die Zielgruppen. Damit niemand eine Kennung rät.",
                "responses": {
                    "200": {
                        "description": "Erledigt.",
                        "content": {
                            "application/json": {
                                "example": {
                                    "rubriken": [
                                        {
                                            "id": 1,
                                            "name": "Ausflugsziele",
                                            "merkmale": [
                                                "is_free",
                                                "is_indoor",
                                                "is_outdoor"
                                            ],
                                            "zeitraum": false
                                        },
                                        {
                                            "id": 7,
                                            "name": "Familienurlaub",
                                            "merkmale": [],
                                            "zeitraum": true
                                        }
                                    ],
                                    "zielgruppen": [
                                        "kleinkinder",
                                        "kinder",
                                        "teenager",
                                        "schulklassen"
                                    ]
                                }
                            }
                        }
                    },
                    "401": {
                        "description": "Kein Token, falsches Token, abgelaufen, widerrufen, oder von einer Adresse, die nicht in der Liste steht.",
                        "content": {
                            "application/problem+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Problem"
                                }
                            }
                        }
                    },
                    "429": {
                        "description": "Zu viele Anfragen. `Retry-After` sagt, wann es weitergeht.",
                        "content": {
                            "application/problem+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Problem"
                                }
                            }
                        }
                    },
                    "403": {
                        "description": "Der Schlüssel trägt dieses Recht nicht. Welche er hat, steht in der Antwort.",
                        "content": {
                            "application/problem+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Problem"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/api/v1/angebote": {
            "get": {
                "summary": "Die eigenen Angebote",
                "description": "Blätterbar mit „seite“ und „pro_seite“ (höchstens 50), eingrenzbar mit „stand“. Ohne Beschreibung: wer blättert, will die Titel sehen.",
                "responses": {
                    "200": {
                        "description": "Erledigt.",
                        "content": {
                            "application/json": {
                                "example": {
                                    "angebote": [
                                        {
                                            "external_id": "OBJ-4711",
                                            "category_id": 7,
                                            "title": "Ferienhaus an der Schlei",
                                            "is_free": false,
                                            "price_from": 480,
                                            "price_to": null,
                                            "age_from": 0,
                                            "age_to": 18,
                                            "street": "Am Deich 3",
                                            "postal_code": "24376",
                                            "city": "Kappeln",
                                            "state": "Schleswig-Holstein",
                                            "country": "DE",
                                            "phone": "04642 123456",
                                            "email": "urlaub@beispiel.test",
                                            "website": "https://www.beispiel.test",
                                            "facebook_url": "",
                                            "instagram_url": "",
                                            "video_url": "",
                                            "is_year_round": true,
                                            "runs_from": null,
                                            "runs_to": null,
                                            "is_accessible": false,
                                            "dogs_allowed": true,
                                            "is_indoor": false,
                                            "is_outdoor": false,
                                            "id": 12345,
                                            "status": "pending",
                                            "slug": "ferienhaus-an-der-schlei",
                                            "district": "Schleswig-Flensburg",
                                            "latitude": "54.661000",
                                            "longitude": "9.932000",
                                            "completeness": 78,
                                            "paused_at": null,
                                            "terms_accepted_at": "2026-10-31 09:12:00",
                                            "created_at": "2026-10-31 09:12:00",
                                            "updated_at": "2026-10-31 09:12:00",
                                            "url": null
                                        }
                                    ],
                                    "gesamt": 41,
                                    "seite": 1,
                                    "pro_seite": 20,
                                    "seiten": 3
                                }
                            }
                        }
                    },
                    "401": {
                        "description": "Kein Token, falsches Token, abgelaufen, widerrufen, oder von einer Adresse, die nicht in der Liste steht.",
                        "content": {
                            "application/problem+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Problem"
                                }
                            }
                        }
                    },
                    "429": {
                        "description": "Zu viele Anfragen. `Retry-After` sagt, wann es weitergeht.",
                        "content": {
                            "application/problem+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Problem"
                                }
                            }
                        }
                    },
                    "403": {
                        "description": "Der Schlüssel trägt dieses Recht nicht. Welche er hat, steht in der Antwort.",
                        "content": {
                            "application/problem+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Problem"
                                }
                            }
                        }
                    }
                }
            },
            "post": {
                "summary": "Ein neues Angebot",
                "description": "Legt an und antwortet mit 201 und der Adresse des Angebots. Es steht danach in der Prüfung, nie direkt online.",
                "responses": {
                    "201": {
                        "description": "Erledigt.",
                        "content": {
                            "application/json": {
                                "example": {
                                    "angebot": {
                                        "external_id": "OBJ-4711",
                                        "category_id": 7,
                                        "title": "Ferienhaus an der Schlei",
                                        "excerpt": "Ein Haus für vier bis sechs Personen, zweihundert Meter hinter dem Deich.",
                                        "description": "<p>Das Haus hat zwei Schlafzimmer, eine Küche und einen eingezäunten Garten.</p>",
                                        "is_free": false,
                                        "price_from": 480,
                                        "price_to": null,
                                        "price_info": "Pro Woche, Nebensaison ab 380 Euro.",
                                        "age_from": 0,
                                        "age_to": 18,
                                        "street": "Am Deich 3",
                                        "postal_code": "24376",
                                        "city": "Kappeln",
                                        "state": "Schleswig-Holstein",
                                        "country": "DE",
                                        "phone": "04642 123456",
                                        "email": "urlaub@beispiel.test",
                                        "website": "https://www.beispiel.test",
                                        "facebook_url": "",
                                        "instagram_url": "",
                                        "video_url": "",
                                        "opening_hours_text": "",
                                        "is_year_round": true,
                                        "runs_from": null,
                                        "runs_to": null,
                                        "is_accessible": false,
                                        "dogs_allowed": true,
                                        "is_indoor": false,
                                        "is_outdoor": false,
                                        "id": 12345,
                                        "status": "pending",
                                        "slug": "ferienhaus-an-der-schlei",
                                        "district": "Schleswig-Flensburg",
                                        "latitude": "54.661000",
                                        "longitude": "9.932000",
                                        "completeness": 78,
                                        "paused_at": null,
                                        "terms_accepted_at": "2026-10-31 09:12:00",
                                        "created_at": "2026-10-31 09:12:00",
                                        "updated_at": "2026-10-31 09:12:00",
                                        "url": null,
                                        "schlagwoerter": [
                                            "Ferienhaus",
                                            "Ostsee"
                                        ],
                                        "zielgruppen": [
                                            "kinder",
                                            "teenager"
                                        ],
                                        "bilder": []
                                    },
                                    "sofort_frei": false
                                }
                            }
                        }
                    },
                    "401": {
                        "description": "Kein Token, falsches Token, abgelaufen, widerrufen, oder von einer Adresse, die nicht in der Liste steht.",
                        "content": {
                            "application/problem+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Problem"
                                }
                            }
                        }
                    },
                    "429": {
                        "description": "Zu viele Anfragen. `Retry-After` sagt, wann es weitergeht.",
                        "content": {
                            "application/problem+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Problem"
                                }
                            }
                        }
                    },
                    "415": {
                        "description": "Beim Schreiben fehlte `Content-Type: application/json`.",
                        "content": {
                            "application/problem+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Problem"
                                }
                            }
                        }
                    },
                    "402": {
                        "description": "Das elfte Angebot ohne Premium, oder ein Bild über der Zahl, die zum Konto gehört.",
                        "content": {
                            "application/problem+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Problem"
                                }
                            }
                        }
                    },
                    "422": {
                        "description": "Ein Feld oder mehrere fallen durch. Welche, steht in `fields`, eine Meldung je Feld.",
                        "content": {
                            "application/problem+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Problem"
                                }
                            }
                        }
                    },
                    "403": {
                        "description": "Der Schlüssel trägt dieses Recht nicht. Welche er hat, steht in der Antwort.",
                        "content": {
                            "application/problem+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Problem"
                                }
                            }
                        }
                    }
                },
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/Angebot"
                            }
                        }
                    }
                }
            }
        },
        "/api/v1/angebote/{id}": {
            "get": {
                "summary": "Ein Angebot",
                "description": "Mit allem, was dazugehört: Bilder, Schlagwörter, Zielgruppen, Stand.",
                "responses": {
                    "200": {
                        "description": "Erledigt.",
                        "content": {
                            "application/json": {
                                "example": {
                                    "angebot": {
                                        "external_id": "OBJ-4711",
                                        "category_id": 7,
                                        "title": "Ferienhaus an der Schlei",
                                        "excerpt": "Ein Haus für vier bis sechs Personen, zweihundert Meter hinter dem Deich.",
                                        "description": "<p>Das Haus hat zwei Schlafzimmer, eine Küche und einen eingezäunten Garten.</p>",
                                        "is_free": false,
                                        "price_from": 480,
                                        "price_to": null,
                                        "price_info": "Pro Woche, Nebensaison ab 380 Euro.",
                                        "age_from": 0,
                                        "age_to": 18,
                                        "street": "Am Deich 3",
                                        "postal_code": "24376",
                                        "city": "Kappeln",
                                        "state": "Schleswig-Holstein",
                                        "country": "DE",
                                        "phone": "04642 123456",
                                        "email": "urlaub@beispiel.test",
                                        "website": "https://www.beispiel.test",
                                        "facebook_url": "",
                                        "instagram_url": "",
                                        "video_url": "",
                                        "opening_hours_text": "",
                                        "is_year_round": true,
                                        "runs_from": null,
                                        "runs_to": null,
                                        "is_accessible": false,
                                        "dogs_allowed": true,
                                        "is_indoor": false,
                                        "is_outdoor": false,
                                        "id": 12345,
                                        "status": "pending",
                                        "slug": "ferienhaus-an-der-schlei",
                                        "district": "Schleswig-Flensburg",
                                        "latitude": "54.661000",
                                        "longitude": "9.932000",
                                        "completeness": 78,
                                        "paused_at": null,
                                        "terms_accepted_at": "2026-10-31 09:12:00",
                                        "created_at": "2026-10-31 09:12:00",
                                        "updated_at": "2026-10-31 09:12:00",
                                        "url": null,
                                        "schlagwoerter": [
                                            "Ferienhaus",
                                            "Ostsee"
                                        ],
                                        "zielgruppen": [
                                            "kinder",
                                            "teenager"
                                        ],
                                        "bilder": []
                                    },
                                    "sofort_frei": false
                                }
                            }
                        }
                    },
                    "401": {
                        "description": "Kein Token, falsches Token, abgelaufen, widerrufen, oder von einer Adresse, die nicht in der Liste steht.",
                        "content": {
                            "application/problem+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Problem"
                                }
                            }
                        }
                    },
                    "429": {
                        "description": "Zu viele Anfragen. `Retry-After` sagt, wann es weitergeht.",
                        "content": {
                            "application/problem+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Problem"
                                }
                            }
                        }
                    },
                    "404": {
                        "description": "Dieses Angebot, dieses Bild oder diesen Pfad gibt es in diesem Konto nicht.",
                        "content": {
                            "application/problem+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Problem"
                                }
                            }
                        }
                    },
                    "403": {
                        "description": "Der Schlüssel trägt dieses Recht nicht. Welche er hat, steht in der Antwort.",
                        "content": {
                            "application/problem+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Problem"
                                }
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    }
                ]
            },
            "patch": {
                "summary": "Eine Teiländerung",
                "description": "Nur die Felder, die dabeistehen; alles andere bleibt. Ein Feld ausdrücklich auf leer zu setzen geht, solange es kein Pflichtfeld ist.",
                "responses": {
                    "200": {
                        "description": "Erledigt.",
                        "content": {
                            "application/json": {
                                "example": {
                                    "angebot": {
                                        "external_id": "OBJ-4711",
                                        "category_id": 7,
                                        "title": "Ferienhaus an der Schlei",
                                        "excerpt": "Ein Haus für vier bis sechs Personen, zweihundert Meter hinter dem Deich.",
                                        "description": "<p>Das Haus hat zwei Schlafzimmer, eine Küche und einen eingezäunten Garten.</p>",
                                        "is_free": false,
                                        "price_from": 480,
                                        "price_to": null,
                                        "price_info": "Pro Woche, Nebensaison ab 380 Euro.",
                                        "age_from": 0,
                                        "age_to": 18,
                                        "street": "Am Deich 3",
                                        "postal_code": "24376",
                                        "city": "Kappeln",
                                        "state": "Schleswig-Holstein",
                                        "country": "DE",
                                        "phone": "04642 123456",
                                        "email": "urlaub@beispiel.test",
                                        "website": "https://www.beispiel.test",
                                        "facebook_url": "",
                                        "instagram_url": "",
                                        "video_url": "",
                                        "opening_hours_text": "",
                                        "is_year_round": true,
                                        "runs_from": null,
                                        "runs_to": null,
                                        "is_accessible": false,
                                        "dogs_allowed": true,
                                        "is_indoor": false,
                                        "is_outdoor": false,
                                        "id": 12345,
                                        "status": "pending",
                                        "slug": "ferienhaus-an-der-schlei",
                                        "district": "Schleswig-Flensburg",
                                        "latitude": "54.661000",
                                        "longitude": "9.932000",
                                        "completeness": 78,
                                        "paused_at": null,
                                        "terms_accepted_at": "2026-10-31 09:12:00",
                                        "created_at": "2026-10-31 09:12:00",
                                        "updated_at": "2026-10-31 09:12:00",
                                        "url": null,
                                        "schlagwoerter": [
                                            "Ferienhaus",
                                            "Ostsee"
                                        ],
                                        "zielgruppen": [
                                            "kinder",
                                            "teenager"
                                        ],
                                        "bilder": []
                                    },
                                    "sofort_frei": false
                                }
                            }
                        }
                    },
                    "401": {
                        "description": "Kein Token, falsches Token, abgelaufen, widerrufen, oder von einer Adresse, die nicht in der Liste steht.",
                        "content": {
                            "application/problem+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Problem"
                                }
                            }
                        }
                    },
                    "429": {
                        "description": "Zu viele Anfragen. `Retry-After` sagt, wann es weitergeht.",
                        "content": {
                            "application/problem+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Problem"
                                }
                            }
                        }
                    },
                    "415": {
                        "description": "Beim Schreiben fehlte `Content-Type: application/json`.",
                        "content": {
                            "application/problem+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Problem"
                                }
                            }
                        }
                    },
                    "404": {
                        "description": "Dieses Angebot, dieses Bild oder diesen Pfad gibt es in diesem Konto nicht.",
                        "content": {
                            "application/problem+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Problem"
                                }
                            }
                        }
                    },
                    "422": {
                        "description": "Ein Feld oder mehrere fallen durch. Welche, steht in `fields`, eine Meldung je Feld.",
                        "content": {
                            "application/problem+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Problem"
                                }
                            }
                        }
                    },
                    "403": {
                        "description": "Der Schlüssel trägt dieses Recht nicht. Welche er hat, steht in der Antwort.",
                        "content": {
                            "application/problem+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Problem"
                                }
                            }
                        }
                    },
                    "409": {
                        "description": "Das Angebot ist zurückgezogen. Es nimmt erst wieder etwas an, wenn jemand es im Konto zurückholt.",
                        "content": {
                            "application/problem+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Problem"
                                }
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/Angebot"
                            }
                        }
                    }
                }
            },
            "delete": {
                "summary": "Zurückziehen",
                "description": "Das Angebot geht aus Listen, Karte und Suche heraus und bleibt zurückzuholen. Endgültig gelöscht wird über die Schnittstelle nichts.",
                "responses": {
                    "204": {
                        "description": "Erledigt."
                    },
                    "401": {
                        "description": "Kein Token, falsches Token, abgelaufen, widerrufen, oder von einer Adresse, die nicht in der Liste steht.",
                        "content": {
                            "application/problem+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Problem"
                                }
                            }
                        }
                    },
                    "429": {
                        "description": "Zu viele Anfragen. `Retry-After` sagt, wann es weitergeht.",
                        "content": {
                            "application/problem+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Problem"
                                }
                            }
                        }
                    },
                    "404": {
                        "description": "Dieses Angebot, dieses Bild oder diesen Pfad gibt es in diesem Konto nicht.",
                        "content": {
                            "application/problem+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Problem"
                                }
                            }
                        }
                    },
                    "403": {
                        "description": "Der Schlüssel trägt dieses Recht nicht. Welche er hat, steht in der Antwort.",
                        "content": {
                            "application/problem+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Problem"
                                }
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    }
                ]
            }
        },
        "/api/v1/angebote/extern/{kennung}": {
            "put": {
                "summary": "Anlegen oder ersetzen",
                "description": "Unter eurer eigenen Objektnummer. Zweimal geschickt heißt einmal angelegt: 201 beim ersten Mal, danach 200.",
                "responses": {
                    "201": {
                        "description": "Erledigt.",
                        "content": {
                            "application/json": {
                                "example": {
                                    "angebot": {
                                        "external_id": "OBJ-4711",
                                        "category_id": 7,
                                        "title": "Ferienhaus an der Schlei",
                                        "excerpt": "Ein Haus für vier bis sechs Personen, zweihundert Meter hinter dem Deich.",
                                        "description": "<p>Das Haus hat zwei Schlafzimmer, eine Küche und einen eingezäunten Garten.</p>",
                                        "is_free": false,
                                        "price_from": 480,
                                        "price_to": null,
                                        "price_info": "Pro Woche, Nebensaison ab 380 Euro.",
                                        "age_from": 0,
                                        "age_to": 18,
                                        "street": "Am Deich 3",
                                        "postal_code": "24376",
                                        "city": "Kappeln",
                                        "state": "Schleswig-Holstein",
                                        "country": "DE",
                                        "phone": "04642 123456",
                                        "email": "urlaub@beispiel.test",
                                        "website": "https://www.beispiel.test",
                                        "facebook_url": "",
                                        "instagram_url": "",
                                        "video_url": "",
                                        "opening_hours_text": "",
                                        "is_year_round": true,
                                        "runs_from": null,
                                        "runs_to": null,
                                        "is_accessible": false,
                                        "dogs_allowed": true,
                                        "is_indoor": false,
                                        "is_outdoor": false,
                                        "id": 12345,
                                        "status": "pending",
                                        "slug": "ferienhaus-an-der-schlei",
                                        "district": "Schleswig-Flensburg",
                                        "latitude": "54.661000",
                                        "longitude": "9.932000",
                                        "completeness": 78,
                                        "paused_at": null,
                                        "terms_accepted_at": "2026-10-31 09:12:00",
                                        "created_at": "2026-10-31 09:12:00",
                                        "updated_at": "2026-10-31 09:12:00",
                                        "url": null,
                                        "schlagwoerter": [
                                            "Ferienhaus",
                                            "Ostsee"
                                        ],
                                        "zielgruppen": [
                                            "kinder",
                                            "teenager"
                                        ],
                                        "bilder": []
                                    },
                                    "sofort_frei": false
                                }
                            }
                        }
                    },
                    "401": {
                        "description": "Kein Token, falsches Token, abgelaufen, widerrufen, oder von einer Adresse, die nicht in der Liste steht.",
                        "content": {
                            "application/problem+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Problem"
                                }
                            }
                        }
                    },
                    "429": {
                        "description": "Zu viele Anfragen. `Retry-After` sagt, wann es weitergeht.",
                        "content": {
                            "application/problem+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Problem"
                                }
                            }
                        }
                    },
                    "415": {
                        "description": "Beim Schreiben fehlte `Content-Type: application/json`.",
                        "content": {
                            "application/problem+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Problem"
                                }
                            }
                        }
                    },
                    "404": {
                        "description": "Dieses Angebot, dieses Bild oder diesen Pfad gibt es in diesem Konto nicht.",
                        "content": {
                            "application/problem+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Problem"
                                }
                            }
                        }
                    },
                    "402": {
                        "description": "Das elfte Angebot ohne Premium, oder ein Bild über der Zahl, die zum Konto gehört.",
                        "content": {
                            "application/problem+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Problem"
                                }
                            }
                        }
                    },
                    "422": {
                        "description": "Ein Feld oder mehrere fallen durch. Welche, steht in `fields`, eine Meldung je Feld.",
                        "content": {
                            "application/problem+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Problem"
                                }
                            }
                        }
                    },
                    "403": {
                        "description": "Der Schlüssel trägt dieses Recht nicht. Welche er hat, steht in der Antwort.",
                        "content": {
                            "application/problem+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Problem"
                                }
                            }
                        }
                    },
                    "409": {
                        "description": "Das Angebot ist zurückgezogen. Es nimmt erst wieder etwas an, wenn jemand es im Konto zurückholt.",
                        "content": {
                            "application/problem+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Problem"
                                }
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "kennung",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/Angebot"
                            }
                        }
                    }
                }
            }
        },
        "/api/v1/angebote/{id}/bilder": {
            "get": {
                "summary": "Die Bilder eines Angebots",
                "description": "Mit drei Breiten je Bild, damit niemand Ordnernamen zusammensetzen muss.",
                "responses": {
                    "200": {
                        "description": "Erledigt.",
                        "content": {
                            "application/json": {
                                "example": {
                                    "bilder": [
                                        {
                                            "id": 90210,
                                            "copyright": "Foto: Ferienhof Beispiel",
                                            "alt_text": "",
                                            "rights_confirmed_at": "2026-10-31 09:12:00",
                                            "klein": "https://www.kids-ontour.de/bilder/angebote/breit400/a1b2c3.jpg",
                                            "mittel": "https://www.kids-ontour.de/bilder/angebote/breit800/a1b2c3.jpg",
                                            "gross": "https://www.kids-ontour.de/bilder/angebote/breit1600/a1b2c3.jpg"
                                        }
                                    ]
                                }
                            }
                        }
                    },
                    "401": {
                        "description": "Kein Token, falsches Token, abgelaufen, widerrufen, oder von einer Adresse, die nicht in der Liste steht.",
                        "content": {
                            "application/problem+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Problem"
                                }
                            }
                        }
                    },
                    "429": {
                        "description": "Zu viele Anfragen. `Retry-After` sagt, wann es weitergeht.",
                        "content": {
                            "application/problem+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Problem"
                                }
                            }
                        }
                    },
                    "404": {
                        "description": "Dieses Angebot, dieses Bild oder diesen Pfad gibt es in diesem Konto nicht.",
                        "content": {
                            "application/problem+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Problem"
                                }
                            }
                        }
                    },
                    "403": {
                        "description": "Der Schlüssel trägt dieses Recht nicht. Welche er hat, steht in der Antwort.",
                        "content": {
                            "application/problem+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Problem"
                                }
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    }
                ]
            },
            "post": {
                "summary": "Ein Bild hochladen",
                "description": "Als „multipart/form-data“ mit „bild“, „copyright“ und „rechte=1“, ein Bild je Anfrage. Ohne Nachweis und ohne Bestätigung der Rechte kein Bild.",
                "responses": {
                    "201": {
                        "description": "Erledigt.",
                        "content": {
                            "application/json": {
                                "example": {
                                    "bilder": [
                                        {
                                            "id": 90210,
                                            "copyright": "Foto: Ferienhof Beispiel",
                                            "alt_text": "",
                                            "rights_confirmed_at": "2026-10-31 09:12:00",
                                            "klein": "https://www.kids-ontour.de/bilder/angebote/breit400/a1b2c3.jpg",
                                            "mittel": "https://www.kids-ontour.de/bilder/angebote/breit800/a1b2c3.jpg",
                                            "gross": "https://www.kids-ontour.de/bilder/angebote/breit1600/a1b2c3.jpg"
                                        }
                                    ]
                                }
                            }
                        }
                    },
                    "401": {
                        "description": "Kein Token, falsches Token, abgelaufen, widerrufen, oder von einer Adresse, die nicht in der Liste steht.",
                        "content": {
                            "application/problem+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Problem"
                                }
                            }
                        }
                    },
                    "429": {
                        "description": "Zu viele Anfragen. `Retry-After` sagt, wann es weitergeht.",
                        "content": {
                            "application/problem+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Problem"
                                }
                            }
                        }
                    },
                    "415": {
                        "description": "Beim Schreiben fehlte `Content-Type: application/json`.",
                        "content": {
                            "application/problem+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Problem"
                                }
                            }
                        }
                    },
                    "404": {
                        "description": "Dieses Angebot, dieses Bild oder diesen Pfad gibt es in diesem Konto nicht.",
                        "content": {
                            "application/problem+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Problem"
                                }
                            }
                        }
                    },
                    "402": {
                        "description": "Das elfte Angebot ohne Premium, oder ein Bild über der Zahl, die zum Konto gehört.",
                        "content": {
                            "application/problem+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Problem"
                                }
                            }
                        }
                    },
                    "422": {
                        "description": "Ein Feld oder mehrere fallen durch. Welche, steht in `fields`, eine Meldung je Feld.",
                        "content": {
                            "application/problem+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Problem"
                                }
                            }
                        }
                    },
                    "403": {
                        "description": "Der Schlüssel trägt dieses Recht nicht. Welche er hat, steht in der Antwort.",
                        "content": {
                            "application/problem+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Problem"
                                }
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    }
                ]
            }
        },
        "/api/v1/angebote/{id}/bilder/{bild}": {
            "delete": {
                "summary": "Ein Bild wegnehmen",
                "description": "Die Datei geht mit, auch die gerechneten Größen.",
                "responses": {
                    "204": {
                        "description": "Erledigt."
                    },
                    "401": {
                        "description": "Kein Token, falsches Token, abgelaufen, widerrufen, oder von einer Adresse, die nicht in der Liste steht.",
                        "content": {
                            "application/problem+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Problem"
                                }
                            }
                        }
                    },
                    "429": {
                        "description": "Zu viele Anfragen. `Retry-After` sagt, wann es weitergeht.",
                        "content": {
                            "application/problem+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Problem"
                                }
                            }
                        }
                    },
                    "404": {
                        "description": "Dieses Angebot, dieses Bild oder diesen Pfad gibt es in diesem Konto nicht.",
                        "content": {
                            "application/problem+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Problem"
                                }
                            }
                        }
                    },
                    "403": {
                        "description": "Der Schlüssel trägt dieses Recht nicht. Welche er hat, steht in der Antwort.",
                        "content": {
                            "application/problem+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Problem"
                                }
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "bild",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    }
                ]
            }
        },
        "/api/v1/angebote/{id}/schlagwoerter": {
            "put": {
                "summary": "Die Schlagwörter setzen",
                "description": "Die ganze Liste auf einmal, höchstens acht. Was es noch nicht gibt, entsteht und wartet auf die Freigabe der Redaktion.",
                "responses": {
                    "200": {
                        "description": "Erledigt.",
                        "content": {
                            "application/json": {
                                "example": {
                                    "schlagwoerter": [
                                        "Ferienhaus",
                                        "Ostsee"
                                    ],
                                    "zielgruppen": [
                                        "kinder",
                                        "teenager"
                                    ]
                                }
                            }
                        }
                    },
                    "401": {
                        "description": "Kein Token, falsches Token, abgelaufen, widerrufen, oder von einer Adresse, die nicht in der Liste steht.",
                        "content": {
                            "application/problem+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Problem"
                                }
                            }
                        }
                    },
                    "429": {
                        "description": "Zu viele Anfragen. `Retry-After` sagt, wann es weitergeht.",
                        "content": {
                            "application/problem+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Problem"
                                }
                            }
                        }
                    },
                    "415": {
                        "description": "Beim Schreiben fehlte `Content-Type: application/json`.",
                        "content": {
                            "application/problem+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Problem"
                                }
                            }
                        }
                    },
                    "404": {
                        "description": "Dieses Angebot, dieses Bild oder diesen Pfad gibt es in diesem Konto nicht.",
                        "content": {
                            "application/problem+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Problem"
                                }
                            }
                        }
                    },
                    "422": {
                        "description": "Ein Feld oder mehrere fallen durch. Welche, steht in `fields`, eine Meldung je Feld.",
                        "content": {
                            "application/problem+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Problem"
                                }
                            }
                        }
                    },
                    "403": {
                        "description": "Der Schlüssel trägt dieses Recht nicht. Welche er hat, steht in der Antwort.",
                        "content": {
                            "application/problem+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Problem"
                                }
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/Angebot"
                            }
                        }
                    }
                }
            }
        },
        "/api/v1/angebote/{id}/zielgruppen": {
            "put": {
                "summary": "Die Zielgruppen setzen",
                "description": "Ebenfalls die ganze Liste. Kleinkinder, Kinder und Teenager folgen sonst aus dem Alter; die Schulklassen sind die eine, die das nicht tut.",
                "responses": {
                    "200": {
                        "description": "Erledigt.",
                        "content": {
                            "application/json": {
                                "example": {
                                    "schlagwoerter": [
                                        "Ferienhaus",
                                        "Ostsee"
                                    ],
                                    "zielgruppen": [
                                        "kinder",
                                        "teenager"
                                    ]
                                }
                            }
                        }
                    },
                    "401": {
                        "description": "Kein Token, falsches Token, abgelaufen, widerrufen, oder von einer Adresse, die nicht in der Liste steht.",
                        "content": {
                            "application/problem+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Problem"
                                }
                            }
                        }
                    },
                    "429": {
                        "description": "Zu viele Anfragen. `Retry-After` sagt, wann es weitergeht.",
                        "content": {
                            "application/problem+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Problem"
                                }
                            }
                        }
                    },
                    "415": {
                        "description": "Beim Schreiben fehlte `Content-Type: application/json`.",
                        "content": {
                            "application/problem+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Problem"
                                }
                            }
                        }
                    },
                    "404": {
                        "description": "Dieses Angebot, dieses Bild oder diesen Pfad gibt es in diesem Konto nicht.",
                        "content": {
                            "application/problem+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Problem"
                                }
                            }
                        }
                    },
                    "422": {
                        "description": "Ein Feld oder mehrere fallen durch. Welche, steht in `fields`, eine Meldung je Feld.",
                        "content": {
                            "application/problem+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Problem"
                                }
                            }
                        }
                    },
                    "403": {
                        "description": "Der Schlüssel trägt dieses Recht nicht. Welche er hat, steht in der Antwort.",
                        "content": {
                            "application/problem+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Problem"
                                }
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/Angebot"
                            }
                        }
                    }
                }
            }
        }
    }
}