# POST /v2/auth/trainer-course

Trainer-Kursentwurf anlegen oder Datei hochladen

Verarbeitet action=create_course (Standard), prepare_title_image, upload_title_image oder upload_external_file. create_course erstellt einen unveröffentlichten Kurs status=0 mit optionalem Termin, Preis, Texten, interner Notiz und Titelbild. prepare_title_image registriert Titelbild-Metadaten und gibt eine signierte Upload-URL zurück. Multipart-Aktionen speichern eine Datei an einem eigenen noch unveröffentlichten Entwurf. Kategorien/Orte/Räume/Tags müssen zur Auswahl gehören, Titelbilder dem Trainer zugeordnet sein. Der persönliche Trainer-Bearer-Code ist erforderlich. Antworten und Upload-URLs sind vertraulich; fünf ungültige Codes innerhalb einer Stunde sperren die IP für eine Stunde. minimum darf maximum nicht übersteigen. Bei gesetztem start oder end sind beide gültigen lokalen Datumswerte erforderlich, end muss nach start liegen. Bei Multipart stehen action und offer direkt neben file, ohne data-JSON. Titelbilder erlauben JPEG/PNG bis 2 MiB und 40 Millionen Pixel, externe Dateien PDF/JPEG/PNG/WebP bis 20 MiB und höchstens zehn je Kurs. title muss nach der Textbereinigung einen nicht leeren Titel ergeben.

- Operation-ID: post_v2_auth_trainer_course
- Dokumentation: /docs/api/v2/operations/post-v2-auth-trainer-course-d982b815ab77/
- OpenAPI-Version: 3.2.1
- Vollständige Spezifikation: /docs/api/v2/openapi.json
- Dokumentationsstatus: reviewed
- Ändert Zustand: ja
- Für Automation freigegeben: nein

## Server

Serverwerte stammen unverändert aus der Datei. Eine lokale Entwicklungsadresse ist keine bestätigte Produktionsadresse. `API_ORIGIN` muss auf die passende Instanz gesetzt werden.

```json
[
  {
    "url": "https://app2.edoobox.com",
    "description": "edoobox API"
  }
]
```

## Authentifizierung

Eine der folgenden Alternativen genügt (ODER). Innerhalb einer Alternative müssen alle Verfahren gemeinsam erfüllt sein (UND).

1. **trainerToken** (HTTP bearer) — Dedicated revocable trainer-course token; not a normal account JWT.

### Wirksame Security Requirements und vollständige Verfahren

```json
{
  "security": [
    {
      "trainerToken": []
    }
  ],
  "securitySchemes": {
    "trainerToken": {
      "type": "http",
      "scheme": "bearer",
      "description": "Dedicated revocable trainer-course token; not a normal account JWT."
    }
  }
}
```

## Parameter

| Name | Ort | Pflicht | Beschreibung |
| --- | --- | --- | --- |
| language | header | nein | Account language code, where the resource supports setLanguage(). |
| Authorization | header | ja | Persönlicher Trainer-Bearer-Code aus der administrativ verwalteten Freigabe; kein gewöhnlicher V2-Admin-Token. |

### Vollständige wirksame Parameterdefinitionen

```json
[
  {
    "name": "language",
    "in": "header",
    "required": false,
    "description": "Account language code, where the resource supports setLanguage().",
    "schema": {
      "type": "string",
      "examples": [
        "de"
      ]
    }
  },
  {
    "name": "Authorization",
    "in": "header",
    "required": true,
    "schema": {
      "type": "string",
      "pattern": "^Bearer [A-Za-z0-9_-]{43}$"
    },
    "description": "Persönlicher Trainer-Bearer-Code aus der administrativ verwalteten Freigabe; kein gewöhnlicher V2-Admin-Token."
  }
]
```

## Anfrage

```json
{
  "required": true,
  "content": {
    "application/json": {
      "schema": {
        "type": "object",
        "properties": {
          "data": {
            "anyOf": [
              {
                "type": "object",
                "properties": {
                  "action": {
                    "const": "create_course",
                    "default": "create_course"
                  },
                  "title": {
                    "type": [
                      "string",
                      "number",
                      "boolean"
                    ],
                    "description": "Skalarer Text; vor Normalisierung höchstens 80 Zeichen. HTML/Leerzeichen werden bereinigt."
                  },
                  "number": {
                    "type": [
                      "string",
                      "number",
                      "boolean"
                    ],
                    "description": "Skalarer Text; vor Normalisierung höchstens 120 Zeichen. HTML/Leerzeichen werden bereinigt."
                  },
                  "category": {
                    "type": "string",
                    "pattern": "^category_[a-f0-9]{12}_-?[0-9]+$",
                    "description": "Öffentliche v2-ID; Beispiel ist synthetisch.",
                    "examples": [
                      "category_012345abcdef_123456"
                    ]
                  },
                  "description": {
                    "type": [
                      "string",
                      "number",
                      "boolean"
                    ],
                    "description": "Skalarer Text; vor Normalisierung höchstens 50000 Zeichen. HTML/Leerzeichen werden bereinigt."
                  },
                  "description_html": {
                    "description": "PHP-empty/Wahrheitsvergleich steuert die HTML-Verarbeitung.",
                    "examples": [
                      true
                    ]
                  },
                  "booking_message": {
                    "description": "Skalarer Text, auf 200000 Zeichen normalisiert; bei HTML-Flag String mit höchstens 50000 Zeichen.",
                    "examples": [
                      "Bitte pünktlich erscheinen."
                    ]
                  },
                  "booking_message_html": {
                    "description": "PHP-empty/Wahrheitsvergleich steuert die HTML-Verarbeitung.",
                    "examples": [
                      true
                    ]
                  },
                  "internal_note": {
                    "description": "Interne Notiz; skalarer Text wird auf 1000 Zeichen normalisiert."
                  },
                  "minimum": {
                    "type": [
                      "number",
                      "string",
                      "null",
                      "boolean"
                    ],
                    "description": "Leertext/null/false gelten als nicht gesetzt; sonst per FILTER_VALIDATE_INT validierte Ganzzahl 0–99999."
                  },
                  "maximum": {
                    "type": [
                      "number",
                      "string",
                      "null",
                      "boolean"
                    ],
                    "description": "Leertext/null/false gelten als nicht gesetzt; sonst per FILTER_VALIDATE_INT validierte Ganzzahl 0–99999."
                  },
                  "price": {
                    "type": [
                      "number",
                      "string",
                      "null",
                      "boolean"
                    ],
                    "description": "Leer/null/false ohne Preis; sonst nichtnegative endliche Zahl bzw. numerischer Text bis 9999999.99, auf zwei Dezimalstellen gerundet."
                  },
                  "start": {
                    "type": [
                      "string",
                      "null",
                      "boolean"
                    ],
                    "description": "YYYY-MM-DDTHH:mm in Kontozeitzone, ohne Sekunden/Offset. Leer/null/false ohne Termin; sobald ein Datum gesetzt ist, sind beide erforderlich."
                  },
                  "end": {
                    "type": [
                      "string",
                      "null",
                      "boolean"
                    ],
                    "description": "YYYY-MM-DDTHH:mm in Kontozeitzone, ohne Sekunden/Offset. Leer/null/false ohne Termin; sobald ein Datum gesetzt ist, sind beide erforderlich."
                  },
                  "place": {
                    "anyOf": [
                      {
                        "type": "string",
                        "pattern": "^place_[a-f0-9]{12}_-?[0-9]+$",
                        "description": "Öffentliche v2-ID; Beispiel ist synthetisch.",
                        "examples": [
                          "place_012345abcdef_123456"
                        ]
                      },
                      {
                        "enum": [
                          "",
                          null,
                          false
                        ]
                      }
                    ]
                  },
                  "room": {
                    "anyOf": [
                      {
                        "type": "string",
                        "pattern": "^room_[a-f0-9]{12}_-?[0-9]+$",
                        "description": "Öffentliche v2-ID; Beispiel ist synthetisch.",
                        "examples": [
                          "room_012345abcdef_123456"
                        ]
                      },
                      {
                        "enum": [
                          "",
                          null,
                          false
                        ]
                      }
                    ]
                  },
                  "tags": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "pattern": "^tag_[a-f0-9]{12}_-?[0-9]+$",
                      "description": "Öffentliche v2-ID; Beispiel ist synthetisch.",
                      "examples": [
                        "tag_012345abcdef_123456"
                      ]
                    },
                    "maxItems": 50
                  },
                  "title_image": {
                    "anyOf": [
                      {
                        "type": "string",
                        "pattern": "^publicfile_[a-f0-9]{12}_-?[0-9]+$",
                        "description": "Öffentliche v2-ID; Beispiel ist synthetisch.",
                        "examples": [
                          "publicfile_012345abcdef_123456"
                        ]
                      },
                      {
                        "enum": [
                          "",
                          null,
                          false
                        ]
                      }
                    ]
                  }
                },
                "additionalProperties": true,
                "required": [
                  "title",
                  "category"
                ]
              },
              {
                "type": "object",
                "properties": {
                  "action": {
                    "const": "prepare_title_image"
                  },
                  "file_name": {
                    "type": [
                      "string",
                      "number",
                      "boolean"
                    ],
                    "description": "Dateiname mit jpg/jpeg/png-Endung; wird als Text auf 180 Zeichen normalisiert."
                  },
                  "file_size": {
                    "type": [
                      "number",
                      "string",
                      "boolean"
                    ],
                    "description": "Per FILTER_VALIDATE_INT geprüfte positive Ganzzahl bis 2097152 Bytes."
                  },
                  "file_type": {
                    "type": "string",
                    "description": "Nach Kleinschreibung/Trimmen image/jpeg oder image/png; Endung muss dazu passen.",
                    "examples": [
                      "image/png"
                    ]
                  }
                },
                "additionalProperties": true,
                "required": [
                  "action",
                  "file_name",
                  "file_size",
                  "file_type"
                ]
              }
            ]
          }
        },
        "additionalProperties": true,
        "required": [
          "data"
        ]
      },
      "examples": {
        "create_course": {
          "value": {
            "data": {
              "action": "create_course",
              "title": "Einführungskurs",
              "category": "category_012345abcdef_123456",
              "number": "K-2027-01",
              "minimum": 1,
              "maximum": 12,
              "price": 120,
              "start": "2027-04-01T09:00",
              "end": "2027-04-01T12:00",
              "tags": []
            }
          }
        },
        "prepare_title_image": {
          "value": {
            "data": {
              "action": "prepare_title_image",
              "file_name": "titelbild.png",
              "file_size": 1024,
              "file_type": "image/png"
            }
          }
        }
      }
    },
    "multipart/form-data": {
      "schema": {
        "type": "object",
        "properties": {
          "action": {
            "type": "string",
            "enum": [
              "upload_title_image",
              "upload_external_file"
            ]
          },
          "offer": {
            "type": "string",
            "pattern": "^offer_[a-f0-9]{12}_-?[0-9]+$",
            "description": "Öffentliche v2-ID; Beispiel ist synthetisch.",
            "examples": [
              "offer_012345abcdef_123456"
            ]
          },
          "file": {
            "type": "string",
            "format": "binary",
            "description": "Titelbild: JPEG/PNG bis 2 MiB und höchstens 40 Millionen Pixel. Externe Datei: PDF/JPEG/PNG/WebP bis 20 MiB, höchstens zehn je Kurs."
          }
        },
        "additionalProperties": true,
        "required": [
          "action",
          "offer",
          "file"
        ]
      }
    }
  }
}
```

## Antworten

HTTP-Status und Antwortinhalt gemeinsam auswerten. Die folgenden Beschreibungen und Schemata sind die Angaben der Quelldatei.

```json
{
  "200": {
    "description": "create_course liefert offer-ID/created/status. prepare_title_image liefert publicfile-ID/insert/upload_url/cdn_url. upload_title_image liefert offer-ID/update/image/files/url; upload_external_file liefert files-ID/insert.",
    "content": {
      "application/json": {
        "example": {
          "success": true,
          "error": false,
          "data": {
            "id": "offer_012345abcdef_123456",
            "created": true,
            "status": 0
          }
        },
        "schema": {
          "type": "object",
          "properties": {
            "success": {
              "type": "boolean"
            },
            "error": {
              "type": [
                "boolean",
                "string"
              ]
            },
            "error_code": {
              "type": "string"
            },
            "data": {
              "anyOf": [
                {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "pattern": "^offer_[a-f0-9]{12}_-?[0-9]+$",
                      "description": "Öffentliche v2-ID; Beispiel ist synthetisch.",
                      "examples": [
                        "offer_012345abcdef_123456"
                      ]
                    },
                    "created": {
                      "const": true
                    },
                    "status": {
                      "const": 0
                    }
                  },
                  "additionalProperties": true,
                  "required": [
                    "id",
                    "created",
                    "status"
                  ]
                },
                {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "pattern": "^publicfile_[a-f0-9]{12}_-?[0-9]+$",
                      "description": "Öffentliche v2-ID; Beispiel ist synthetisch.",
                      "examples": [
                        "publicfile_012345abcdef_123456"
                      ]
                    },
                    "insert": {
                      "const": true
                    },
                    "upload_url": {
                      "type": "string",
                      "format": "uri",
                      "description": "Vertrauliche signierte Upload-URL."
                    },
                    "cdn_url": {
                      "type": "string",
                      "format": "uri"
                    }
                  },
                  "additionalProperties": true,
                  "required": [
                    "id",
                    "insert",
                    "upload_url",
                    "cdn_url"
                  ]
                },
                {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "pattern": "^offer_[a-f0-9]{12}_-?[0-9]+$",
                      "description": "Öffentliche v2-ID; Beispiel ist synthetisch.",
                      "examples": [
                        "offer_012345abcdef_123456"
                      ]
                    },
                    "update": {
                      "const": true
                    },
                    "image": {
                      "type": "string"
                    },
                    "files": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "url": {
                      "type": "string",
                      "description": "Öffentliche Bild-URL."
                    }
                  },
                  "additionalProperties": true,
                  "required": [
                    "id",
                    "update",
                    "image",
                    "files",
                    "url"
                  ]
                },
                {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "pattern": "^files_[a-f0-9]{12}_-?[0-9]+$",
                      "description": "Öffentliche v2-ID; Beispiel ist synthetisch.",
                      "examples": [
                        "files_012345abcdef_123456"
                      ]
                    },
                    "insert": {
                      "const": true
                    }
                  },
                  "additionalProperties": true,
                  "required": [
                    "id",
                    "insert"
                  ]
                }
              ]
            },
            "type": {
              "type": "string"
            }
          },
          "additionalProperties": true
        }
      }
    }
  },
  "400": {
    "description": "Invalid request.",
    "content": {
      "application/json": {
        "example": {
          "success": false,
          "error": "ED40006"
        }
      }
    }
  },
  "401": {
    "description": "Missing/expired token, missing account context, account paused or model-specific authorization failure.",
    "content": {
      "application/json": {
        "example": {
          "success": false,
          "error": "ED40103"
        }
      }
    }
  },
  "402": {
    "description": "Missing/invalid action data or subscription/operation restriction.",
    "content": {
      "application/json": {
        "example": {
          "success": false,
          "error": "ED80100"
        }
      }
    }
  },
  "403": {
    "description": "Permission denied. Browser CORS rejection may have an empty body.",
    "content": {
      "application/json": {
        "example": {
          "success": false,
          "error": "ED40340"
        }
      }
    }
  },
  "404": {
    "description": "Unknown resource/action or unsupported method.",
    "content": {
      "application/json": {
        "example": {
          "success": false,
          "error": "ED80019"
        }
      }
    }
  },
  "422": {
    "description": "Validation failed.",
    "content": {
      "application/json": {
        "example": {
          "success": false,
          "error": "ED80020"
        }
      }
    }
  },
  "429": {
    "description": "Request/login rate limit exceeded. Honor Retry-After when supplied; legacy rate-limit paths may omit it. Retry only proven reads within a bounded deadline; a mutation is never automatically replayed after an ambiguous response.",
    "content": {
      "application/json": {
        "example": {
          "success": false,
          "error": "ED42902"
        }
      }
    },
    "headers": {
      "Retry-After": {
        "description": "Optional server delay in whole seconds or an HTTP date. Absence is not permission for an immediate retry.",
        "schema": {
          "type": "string",
          "examples": [
            "30"
          ]
        }
      }
    }
  },
  "502": {
    "description": "Server-side operation failed.",
    "content": {
      "application/json": {
        "example": {
          "success": false,
          "error": "ED50201"
        }
      }
    }
  }
}
```

## Aufrufbeispiele

Anpassen vor dem Ausführen. Platzhalter und veröffentlichte Beispiele sind synthetisch. Die Beispiele senden hier keine Anfrage; sie ergänzen keine unbekannten Pflichtfelder.

### cURL

```sh
# Request Body: synthetisches Beispiel aus der Spezifikation; Werte und Vollständigkeit vor Verwendung prüfen.
: "${API_ORIGIN:?API_ORIGIN setzen}"
: "${AUTH_TRAINERTOKEN:?AUTH_TRAINERTOKEN setzen}"
: "${HEADER_AUTHORIZATION:?HEADER_AUTHORIZATION setzen}"
cat > payload.json <<'EDOOBOX_JSON_EXAMPLE'
{
  "data": {
    "action": "create_course",
    "title": "Einführungskurs",
    "category": "category_012345abcdef_123456",
    "number": "K-2027-01",
    "minimum": 1,
    "maximum": 12,
    "price": 120,
    "start": "2027-04-01T09:00",
    "end": "2027-04-01T12:00",
    "tags": []
  }
}
EDOOBOX_JSON_EXAMPLE
curl --request 'POST' \
  --url "${API_ORIGIN%/}"'/v2/auth/trainer-course' \
  --header 'Authorization: Bearer '"${AUTH_TRAINERTOKEN}" \
  --header 'Content-Type: application/json' \
  --data-binary '@payload.json'
```

### JavaScript (Node.js)

```javascript
// Request Body: synthetisches Beispiel aus der Spezifikation; Werte und Vollständigkeit vor Verwendung prüfen.
const required = (name) => {
  const value = process.env[name];
  if (!value) throw new Error(`Set ${name}`);
  return value;
};
let path = "/v2/auth/trainer-course";
const origin = required('API_ORIGIN');
const url = new URL((origin.endsWith('/') ? origin.slice(0, -1) : origin) + path);
const headers = {};
headers["Authorization"] = "Bearer " + required("AUTH_TRAINERTOKEN");
headers['Content-Type'] = "application/json";
const body = JSON.stringify({
  "data": {
    "action": "create_course",
    "title": "Einführungskurs",
    "category": "category_012345abcdef_123456",
    "number": "K-2027-01",
    "minimum": 1,
    "maximum": 12,
    "price": 120,
    "start": "2027-04-01T09:00",
    "end": "2027-04-01T12:00",
    "tags": []
  }
});
const response = await fetch(url, { method: "POST", headers, body, redirect: 'manual' });
const text = await response.text();
console.log(response.status, text);
// HTTP-Status und dokumentierte error/success-Felder gemeinsam prüfen.
```

### Python

```python
# Request Body: synthetisches Beispiel aus der Spezifikation; Werte und Vollständigkeit vor Verwendung prüfen.
import os
import urllib.parse
import urllib.request
import urllib.error

path = "/v2/auth/trainer-course"
url = os.environ["API_ORIGIN"].rstrip("/") + path
headers = {}
headers["Authorization"] = "Bearer " + os.environ["AUTH_TRAINERTOKEN"]
headers["Content-Type"] = "application/json"
body = "{\"data\":{\"action\":\"create_course\",\"title\":\"Einführungskurs\",\"category\":\"category_012345abcdef_123456\",\"number\":\"K-2027-01\",\"minimum\":1,\"maximum\":12,\"price\":120,\"start\":\"2027-04-01T09:00\",\"end\":\"2027-04-01T12:00\",\"tags\":[]}}".encode("utf-8")

# Redirects nicht automatisch verfolgen.
class NoRedirect(urllib.request.HTTPRedirectHandler):
    def redirect_request(self, req, fp, code, msg, headers, newurl):
        return None
request = urllib.request.Request(url, headers=headers, method="POST", data=body)
try:
    response = urllib.request.build_opener(NoRedirect).open(request, timeout=30)
except urllib.error.HTTPError as error:
    response = error
print(response.status, response.read().decode("utf-8", errors="replace"))
# HTTP-Status und dokumentierte error/success-Felder gemeinsam prüfen.
```

## Vollständige Operationsdefinition und edoobox-Hinweise

Alle Originalfelder einschliesslich `x-edoobox-*`, requestBody und responses bleiben erhalten. Teilweise dokumentierte Schemata sind kein vollständiger Validierungsvertrag.

```json
{
  "operationId": "post_v2_auth_trainer_course",
  "summary": "Trainer-Kursentwurf anlegen oder Datei hochladen",
  "description": "Verarbeitet action=create_course (Standard), prepare_title_image, upload_title_image oder upload_external_file. create_course erstellt einen unveröffentlichten Kurs status=0 mit optionalem Termin, Preis, Texten, interner Notiz und Titelbild. prepare_title_image registriert Titelbild-Metadaten und gibt eine signierte Upload-URL zurück. Multipart-Aktionen speichern eine Datei an einem eigenen noch unveröffentlichten Entwurf. Kategorien/Orte/Räume/Tags müssen zur Auswahl gehören, Titelbilder dem Trainer zugeordnet sein. Der persönliche Trainer-Bearer-Code ist erforderlich. Antworten und Upload-URLs sind vertraulich; fünf ungültige Codes innerhalb einer Stunde sperren die IP für eine Stunde. minimum darf maximum nicht übersteigen. Bei gesetztem start oder end sind beide gültigen lokalen Datumswerte erforderlich, end muss nach start liegen. Bei Multipart stehen action und offer direkt neben file, ohne data-JSON. Titelbilder erlauben JPEG/PNG bis 2 MiB und 40 Millionen Pixel, externe Dateien PDF/JPEG/PNG/WebP bis 20 MiB und höchstens zehn je Kurs. title muss nach der Textbereinigung einen nicht leeren Titel ergeben.",
  "tags": [
    "trainer-course"
  ],
  "security": [
    {
      "trainerToken": []
    }
  ],
  "parameters": [
    {
      "name": "language",
      "in": "header",
      "required": false,
      "description": "Account language code, where the resource supports setLanguage().",
      "schema": {
        "type": "string",
        "examples": [
          "de"
        ]
      }
    },
    {
      "name": "Authorization",
      "in": "header",
      "required": true,
      "schema": {
        "type": "string",
        "pattern": "^Bearer [A-Za-z0-9_-]{43}$"
      },
      "description": "Persönlicher Trainer-Bearer-Code aus der administrativ verwalteten Freigabe; kein gewöhnlicher V2-Admin-Token."
    }
  ],
  "responses": {
    "200": {
      "description": "create_course liefert offer-ID/created/status. prepare_title_image liefert publicfile-ID/insert/upload_url/cdn_url. upload_title_image liefert offer-ID/update/image/files/url; upload_external_file liefert files-ID/insert.",
      "content": {
        "application/json": {
          "example": {
            "success": true,
            "error": false,
            "data": {
              "id": "offer_012345abcdef_123456",
              "created": true,
              "status": 0
            }
          },
          "schema": {
            "type": "object",
            "properties": {
              "success": {
                "type": "boolean"
              },
              "error": {
                "type": [
                  "boolean",
                  "string"
                ]
              },
              "error_code": {
                "type": "string"
              },
              "data": {
                "anyOf": [
                  {
                    "type": "object",
                    "properties": {
                      "id": {
                        "type": "string",
                        "pattern": "^offer_[a-f0-9]{12}_-?[0-9]+$",
                        "description": "Öffentliche v2-ID; Beispiel ist synthetisch.",
                        "examples": [
                          "offer_012345abcdef_123456"
                        ]
                      },
                      "created": {
                        "const": true
                      },
                      "status": {
                        "const": 0
                      }
                    },
                    "additionalProperties": true,
                    "required": [
                      "id",
                      "created",
                      "status"
                    ]
                  },
                  {
                    "type": "object",
                    "properties": {
                      "id": {
                        "type": "string",
                        "pattern": "^publicfile_[a-f0-9]{12}_-?[0-9]+$",
                        "description": "Öffentliche v2-ID; Beispiel ist synthetisch.",
                        "examples": [
                          "publicfile_012345abcdef_123456"
                        ]
                      },
                      "insert": {
                        "const": true
                      },
                      "upload_url": {
                        "type": "string",
                        "format": "uri",
                        "description": "Vertrauliche signierte Upload-URL."
                      },
                      "cdn_url": {
                        "type": "string",
                        "format": "uri"
                      }
                    },
                    "additionalProperties": true,
                    "required": [
                      "id",
                      "insert",
                      "upload_url",
                      "cdn_url"
                    ]
                  },
                  {
                    "type": "object",
                    "properties": {
                      "id": {
                        "type": "string",
                        "pattern": "^offer_[a-f0-9]{12}_-?[0-9]+$",
                        "description": "Öffentliche v2-ID; Beispiel ist synthetisch.",
                        "examples": [
                          "offer_012345abcdef_123456"
                        ]
                      },
                      "update": {
                        "const": true
                      },
                      "image": {
                        "type": "string"
                      },
                      "files": {
                        "type": "array",
                        "items": {
                          "type": "string"
                        }
                      },
                      "url": {
                        "type": "string",
                        "description": "Öffentliche Bild-URL."
                      }
                    },
                    "additionalProperties": true,
                    "required": [
                      "id",
                      "update",
                      "image",
                      "files",
                      "url"
                    ]
                  },
                  {
                    "type": "object",
                    "properties": {
                      "id": {
                        "type": "string",
                        "pattern": "^files_[a-f0-9]{12}_-?[0-9]+$",
                        "description": "Öffentliche v2-ID; Beispiel ist synthetisch.",
                        "examples": [
                          "files_012345abcdef_123456"
                        ]
                      },
                      "insert": {
                        "const": true
                      }
                    },
                    "additionalProperties": true,
                    "required": [
                      "id",
                      "insert"
                    ]
                  }
                ]
              },
              "type": {
                "type": "string"
              }
            },
            "additionalProperties": true
          }
        }
      }
    },
    "400": {
      "description": "Invalid request.",
      "content": {
        "application/json": {
          "example": {
            "success": false,
            "error": "ED40006"
          }
        }
      }
    },
    "401": {
      "description": "Missing/expired token, missing account context, account paused or model-specific authorization failure.",
      "content": {
        "application/json": {
          "example": {
            "success": false,
            "error": "ED40103"
          }
        }
      }
    },
    "402": {
      "description": "Missing/invalid action data or subscription/operation restriction.",
      "content": {
        "application/json": {
          "example": {
            "success": false,
            "error": "ED80100"
          }
        }
      }
    },
    "403": {
      "description": "Permission denied. Browser CORS rejection may have an empty body.",
      "content": {
        "application/json": {
          "example": {
            "success": false,
            "error": "ED40340"
          }
        }
      }
    },
    "404": {
      "description": "Unknown resource/action or unsupported method.",
      "content": {
        "application/json": {
          "example": {
            "success": false,
            "error": "ED80019"
          }
        }
      }
    },
    "422": {
      "description": "Validation failed.",
      "content": {
        "application/json": {
          "example": {
            "success": false,
            "error": "ED80020"
          }
        }
      }
    },
    "429": {
      "description": "Request/login rate limit exceeded. Honor Retry-After when supplied; legacy rate-limit paths may omit it. Retry only proven reads within a bounded deadline; a mutation is never automatically replayed after an ambiguous response.",
      "content": {
        "application/json": {
          "example": {
            "success": false,
            "error": "ED42902"
          }
        }
      },
      "headers": {
        "Retry-After": {
          "description": "Optional server delay in whole seconds or an HTTP date. Absence is not permission for an immediate retry.",
          "schema": {
            "type": "string",
            "examples": [
              "30"
            ]
          }
        }
      }
    },
    "502": {
      "description": "Server-side operation failed.",
      "content": {
        "application/json": {
          "example": {
            "success": false,
            "error": "ED50201"
          }
        }
      }
    }
  },
  "x-edoobox-mutates-state": true,
  "requestBody": {
    "required": true,
    "content": {
      "application/json": {
        "schema": {
          "type": "object",
          "properties": {
            "data": {
              "anyOf": [
                {
                  "type": "object",
                  "properties": {
                    "action": {
                      "const": "create_course",
                      "default": "create_course"
                    },
                    "title": {
                      "type": [
                        "string",
                        "number",
                        "boolean"
                      ],
                      "description": "Skalarer Text; vor Normalisierung höchstens 80 Zeichen. HTML/Leerzeichen werden bereinigt."
                    },
                    "number": {
                      "type": [
                        "string",
                        "number",
                        "boolean"
                      ],
                      "description": "Skalarer Text; vor Normalisierung höchstens 120 Zeichen. HTML/Leerzeichen werden bereinigt."
                    },
                    "category": {
                      "type": "string",
                      "pattern": "^category_[a-f0-9]{12}_-?[0-9]+$",
                      "description": "Öffentliche v2-ID; Beispiel ist synthetisch.",
                      "examples": [
                        "category_012345abcdef_123456"
                      ]
                    },
                    "description": {
                      "type": [
                        "string",
                        "number",
                        "boolean"
                      ],
                      "description": "Skalarer Text; vor Normalisierung höchstens 50000 Zeichen. HTML/Leerzeichen werden bereinigt."
                    },
                    "description_html": {
                      "description": "PHP-empty/Wahrheitsvergleich steuert die HTML-Verarbeitung.",
                      "examples": [
                        true
                      ]
                    },
                    "booking_message": {
                      "description": "Skalarer Text, auf 200000 Zeichen normalisiert; bei HTML-Flag String mit höchstens 50000 Zeichen.",
                      "examples": [
                        "Bitte pünktlich erscheinen."
                      ]
                    },
                    "booking_message_html": {
                      "description": "PHP-empty/Wahrheitsvergleich steuert die HTML-Verarbeitung.",
                      "examples": [
                        true
                      ]
                    },
                    "internal_note": {
                      "description": "Interne Notiz; skalarer Text wird auf 1000 Zeichen normalisiert."
                    },
                    "minimum": {
                      "type": [
                        "number",
                        "string",
                        "null",
                        "boolean"
                      ],
                      "description": "Leertext/null/false gelten als nicht gesetzt; sonst per FILTER_VALIDATE_INT validierte Ganzzahl 0–99999."
                    },
                    "maximum": {
                      "type": [
                        "number",
                        "string",
                        "null",
                        "boolean"
                      ],
                      "description": "Leertext/null/false gelten als nicht gesetzt; sonst per FILTER_VALIDATE_INT validierte Ganzzahl 0–99999."
                    },
                    "price": {
                      "type": [
                        "number",
                        "string",
                        "null",
                        "boolean"
                      ],
                      "description": "Leer/null/false ohne Preis; sonst nichtnegative endliche Zahl bzw. numerischer Text bis 9999999.99, auf zwei Dezimalstellen gerundet."
                    },
                    "start": {
                      "type": [
                        "string",
                        "null",
                        "boolean"
                      ],
                      "description": "YYYY-MM-DDTHH:mm in Kontozeitzone, ohne Sekunden/Offset. Leer/null/false ohne Termin; sobald ein Datum gesetzt ist, sind beide erforderlich."
                    },
                    "end": {
                      "type": [
                        "string",
                        "null",
                        "boolean"
                      ],
                      "description": "YYYY-MM-DDTHH:mm in Kontozeitzone, ohne Sekunden/Offset. Leer/null/false ohne Termin; sobald ein Datum gesetzt ist, sind beide erforderlich."
                    },
                    "place": {
                      "anyOf": [
                        {
                          "type": "string",
                          "pattern": "^place_[a-f0-9]{12}_-?[0-9]+$",
                          "description": "Öffentliche v2-ID; Beispiel ist synthetisch.",
                          "examples": [
                            "place_012345abcdef_123456"
                          ]
                        },
                        {
                          "enum": [
                            "",
                            null,
                            false
                          ]
                        }
                      ]
                    },
                    "room": {
                      "anyOf": [
                        {
                          "type": "string",
                          "pattern": "^room_[a-f0-9]{12}_-?[0-9]+$",
                          "description": "Öffentliche v2-ID; Beispiel ist synthetisch.",
                          "examples": [
                            "room_012345abcdef_123456"
                          ]
                        },
                        {
                          "enum": [
                            "",
                            null,
                            false
                          ]
                        }
                      ]
                    },
                    "tags": {
                      "type": "array",
                      "items": {
                        "type": "string",
                        "pattern": "^tag_[a-f0-9]{12}_-?[0-9]+$",
                        "description": "Öffentliche v2-ID; Beispiel ist synthetisch.",
                        "examples": [
                          "tag_012345abcdef_123456"
                        ]
                      },
                      "maxItems": 50
                    },
                    "title_image": {
                      "anyOf": [
                        {
                          "type": "string",
                          "pattern": "^publicfile_[a-f0-9]{12}_-?[0-9]+$",
                          "description": "Öffentliche v2-ID; Beispiel ist synthetisch.",
                          "examples": [
                            "publicfile_012345abcdef_123456"
                          ]
                        },
                        {
                          "enum": [
                            "",
                            null,
                            false
                          ]
                        }
                      ]
                    }
                  },
                  "additionalProperties": true,
                  "required": [
                    "title",
                    "category"
                  ]
                },
                {
                  "type": "object",
                  "properties": {
                    "action": {
                      "const": "prepare_title_image"
                    },
                    "file_name": {
                      "type": [
                        "string",
                        "number",
                        "boolean"
                      ],
                      "description": "Dateiname mit jpg/jpeg/png-Endung; wird als Text auf 180 Zeichen normalisiert."
                    },
                    "file_size": {
                      "type": [
                        "number",
                        "string",
                        "boolean"
                      ],
                      "description": "Per FILTER_VALIDATE_INT geprüfte positive Ganzzahl bis 2097152 Bytes."
                    },
                    "file_type": {
                      "type": "string",
                      "description": "Nach Kleinschreibung/Trimmen image/jpeg oder image/png; Endung muss dazu passen.",
                      "examples": [
                        "image/png"
                      ]
                    }
                  },
                  "additionalProperties": true,
                  "required": [
                    "action",
                    "file_name",
                    "file_size",
                    "file_type"
                  ]
                }
              ]
            }
          },
          "additionalProperties": true,
          "required": [
            "data"
          ]
        },
        "examples": {
          "create_course": {
            "value": {
              "data": {
                "action": "create_course",
                "title": "Einführungskurs",
                "category": "category_012345abcdef_123456",
                "number": "K-2027-01",
                "minimum": 1,
                "maximum": 12,
                "price": 120,
                "start": "2027-04-01T09:00",
                "end": "2027-04-01T12:00",
                "tags": []
              }
            }
          },
          "prepare_title_image": {
            "value": {
              "data": {
                "action": "prepare_title_image",
                "file_name": "titelbild.png",
                "file_size": 1024,
                "file_type": "image/png"
              }
            }
          }
        }
      },
      "multipart/form-data": {
        "schema": {
          "type": "object",
          "properties": {
            "action": {
              "type": "string",
              "enum": [
                "upload_title_image",
                "upload_external_file"
              ]
            },
            "offer": {
              "type": "string",
              "pattern": "^offer_[a-f0-9]{12}_-?[0-9]+$",
              "description": "Öffentliche v2-ID; Beispiel ist synthetisch.",
              "examples": [
                "offer_012345abcdef_123456"
              ]
            },
            "file": {
              "type": "string",
              "format": "binary",
              "description": "Titelbild: JPEG/PNG bis 2 MiB und höchstens 40 Millionen Pixel. Externe Datei: PDF/JPEG/PNG/WebP bis 20 MiB, höchstens zehn je Kurs."
            }
          },
          "additionalProperties": true,
          "required": [
            "action",
            "offer",
            "file"
          ]
        }
      }
    }
  },
  "x-edoobox-authentication-lifecycle": true,
  "x-edoobox-runtimeMutationGuard": false,
  "x-edoobox-documentationReview": {
    "file": "docs/openapi/operation-contracts/10-other-resources.json",
    "notes": []
  },
  "x-edoobox-source": {
    "file": "application/models/v2/Trainercourse.php",
    "method": "handlePublicRequest",
    "line": 88
  },
  "x-edoobox-documentationStatus": "reviewed",
  "x-edoobox-readOnly": false,
  "x-edoobox-safeForAutomation": false,
  "x-edoobox-permission": {
    "resource": "trainer-course",
    "short": null,
    "checkedByDispatcher": false,
    "levels": [],
    "description": "No account-JWT resource permission levels are applied. Authentication requirements, if any, are defined by this route source-specific boundary and dedicated security scheme."
  }
}
```
