# Standorte

Ein Standort ist der Ort, an dem Displays hängen: Name, Anschrift, Zeitzone, optional die Lage auf der Karte, das Bundesland, die Öffnungszeiten und die Art des Ladens. Diese Seite nennt die Felder, die Routen, die Zuordnung eines Displays und die Stellen, an denen der Standort sonst noch auftaucht.

> **Typ:** nachschlagen
> **Für wen:** Wer eine Filialstruktur aus einem eigenen System spiegelt oder Auswertungen nach Orten gruppiert.
> **Voraussetzung:** ein Schlüssel `ak_…` mit den Rechten `location.*` — sie sind schlüsselfähig

## Was ein Standort ist

Ein Ordner über Displays, kein zweiter Mandant. Er trägt keine Rechte, keine Lizenzen und keinen eigenen Bestand; ein Display zeigt mit `locationId` auf ihn, mehr Bindung gibt es nicht. Etiketten und Leinwände bleiben, was sie sind: Der Standort ersetzt sie nicht, er beantwortet eine andere Frage.

| Feld | Regel |
|---|---|
| `id` | `loc_` und 26 Zeichen |
| `name` | 1 bis 120 Zeichen, je Organisation eindeutig |
| `address` | vier Pflichtfelder, sobald überhaupt eine Anschrift da ist: `street`, `postalCode`, `city`, `country` (Ländercode nach ISO 3166-1 alpha-2, groß geschrieben); `null` heißt „keine hinterlegt“ |
| `timeZone` | Pflicht, eine Zone der IANA-Datenbank (`Europe/Berlin`) |
| `coordinates` | `lat` und `lng` oder `null`; vom Menschen gesetzt, ohne Genauigkeitsangabe |
| `region` | das Bundesland als Kennung nach ISO 3166-2 (`DE-HH`, heute die sechzehn deutschen Länder) oder `null`; bestimmt die gesetzlichen Feiertage |
| `openingHours` | `{ weekly, specialDays }` oder `null`, solange keine Zeiten gepflegt sind, siehe [Öffnungszeiten](#öffnungszeiten) |
| `note` | Freitext bis 500 Zeichen für die Leute vor Ort |
| `venueType` | die Art des Ladens (`pharmacy`, `workshop`, `bakery`, `beverage_store`, `grocery`, `kiosk`, `fitness_studio`, `medical_practice`, `retail`, `other`) oder `null`; Werbebuchungen zielen danach, und die Auswertung fasst danach zusammen, ebenso nach dem Bundesland |
| `version` | die Fassung für `If-Match`, siehe [ETag und If-Match](/de/api/etag-and-if-match) |

Die Zeitzone gehört an den Ort und nicht an das Display: Zwei Bildschirme im selben Raum haben nie zwei verschiedene.

## Die Routen

| Route | Recht | Bemerkung |
|---|---|---|
| `GET /v1/locations` | `location.list` | vollständig, ohne Blättern; jede Zeile nennt mit `playerCount` die Zahl der Displays am Ort |
| `GET /v1/locations/{id}` | `location.read` | ein Ort mit allen Angaben |
| `POST /v1/locations` | `location.create` | `201`; braucht die Fähigkeit **Standorte** der Organisation |
| `PATCH /v1/locations/{id}` | `location.update` | Teiländerung; `address: null`, `coordinates: null`, `region: null`, `openingHours: null` und `venueType: null` entfernen die Angabe, ein ausgelassenes Feld bleibt stehen. `openingHours` ersetzt den bisherigen Stand als Ganzes |
| `DELETE /v1/locations/{id}` | `location.delete` | nur ein leerer Ort |

```bash
curl -sS -X POST "$SZENERA_API/v1/locations" \
  -H "Authorization: Bearer $SZENERA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
        "name": "Filiale Nord",
        "timeZone": "Europe/Berlin",
        "address": { "street": "Nordstraße 12", "postalCode": "30159", "city": "Hannover", "country": "DE" }
      }'
```

## Ein Display zuordnen

Die Zuordnung läuft nicht über die Standort-Route, sondern über das Display: Ein Ort ist ein Stammdatum des Displays wie sein Name.

```bash
curl -sS -X PATCH "$SZENERA_API/v1/players/ply_01KFB7QPW0PLAYER0000000001" \
  -H "Authorization: Bearer $SZENERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "If-Match: 3" \
  -d '{ "locationId": "loc_01KFB7QPW0LOCATION00000001" }'
```

`"locationId": null` löst das Display wieder. Ein Standort reißt nie Displays mit sich: Ein belegter Ort lässt sich nicht löschen, die Displays werden vorher umgehängt oder gelöst. Bekommt ein Display einen anderen Standort oder keinen mehr, baut es seine Momentaufnahme sofort neu: Öffnungszeiten, Notdienst, Wetter und Zeitzone folgen dem neuen Ort.

## Öffnungszeiten

`openingHours` trägt den Wochenplan und die Sondertermine. Die Zeiten sind Wanduhrzeiten in der Zeitzone des Standorts.

| Feld | Regel |
|---|---|
| `weekly.mon` … `weekly.sun` | je Tag bis zu sechs Fenster `{ "opens": "08:00", "closes": "13:00" }`, aufsteigend und ohne Überschneidung, leer heißt geschlossen. Liegt `closes` nicht nach `opens`, endet das Fenster am Folgetag (`22:00` bis `02:00`), gleiche Uhrzeiten heißen 24 Stunden (`00:00` bis `00:00` ist durchgehend geöffnet). Ein Fenster über Mitternacht ist das letzte des Tages und reicht nicht in das erste Fenster des Folgetags. Ein fehlender Tag gilt als geschlossen |
| `specialDays[]` | bis zu 100 Einträge `{ startDate, endDate, label, closed, windows }`: ein Tag oder Zeitraum bis 92 Tage (`endDate` einschließlich), eine Beschriftung bis 60 Zeichen, geschlossen ohne Fenster oder geöffnet mit mindestens einem. Sondertermine überschneiden sich nicht |

Was an einem Tag gilt, folgt einer festen Rangfolge: ein Sondertermin, sonst ein gesetzlicher Feiertag des Bundeslandes (geschlossen), sonst der Wochenplan. Die Feiertage speichert die Plattform nicht, sie rechnet sie je Bundesland aus dem Osterdatum und den Landesfeiertagen. Feiertage einzelner Gemeinden trägt die Organisation als Sondertermin ein.

```bash
curl -sS -X PATCH "$SZENERA_API/v1/locations/loc_01KFB7QPW0LOCATION00000001" \
  -H "Authorization: Bearer $SZENERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H 'If-Match: "3"' \
  -d '{
    "region": "DE-HH",
    "openingHours": {
      "weekly": {
        "mon": [{ "opens": "08:30", "closes": "13:00" }, { "opens": "14:00", "closes": "18:30" }],
        "sat": [{ "opens": "09:00", "closes": "14:00" }]
      },
      "specialDays": [
        { "startDate": "2026-12-24", "endDate": "2026-12-24", "label": "Heiligabend",
          "closed": false, "windows": [{ "opens": "08:30", "closes": "12:00" }] }
      ]
    }
  }'
```

Ändern sich Wochenplan, Sondertermine, Bundesland oder Zeitzone, entsteht neben `fleet.location.updated` das Ereignis `fleet.location.opening-hours-changed`. Alle Displays der Organisation bauen daraufhin ihre Momentaufnahme neu, denn ein Widget kann die Zeiten jedes Standorts zeigen, nicht nur die des eigenen. Wer sich nicht ändert, bekommt nichts geschickt, die übrigen zeigen die neuen Zeiten nach wenigen Sekunden. Dasselbe gilt, wenn ein Standort gelöscht wird.

## Wo der Standort sonst auftaucht

| Stelle | Wie |
|---|---|
| Display-Liste | `GET /v1/players?locationId=loc_…` filtert auf einen Ort |
| Auswertung der Wiedergabe | `location` ist eine Gruppierungs-Achse; gruppiert wird über die **heutige** Zuordnung, ein Display ohne Ort fällt in eine eigene Zeile |
| Kommandozeile | die Gruppe `location`, dazu `player assign-location` und `player unassign-location` — siehe [Befehle für Ihre Organisation](/de/cli/commands-organization) |
| Assistent | die Werkzeuge `list_locations`, `get_location`, `assign_player_location`, `create_location` und `set_location_opening_hours` des [MCP-Dienstes](/de/api/mcp-service) |
| Apps | der Standort des Displays im Resolver-Kontext, mit `location-hours` samt Öffnungszeiten, siehe [Datenzugriff, Rechte und Bedingungen](/de/apps-sdk/data-access) |

## Wenn etwas nicht klappt

| Symptom | Bedeutung | Lösung |
|---|---|---|
| `409 Location.HasPlayers` | Am Ort hängen noch Displays. Die Antwort nennt die Zahl. | Die Displays umhängen oder lösen, dann löschen. |
| `409 Location.NameAlreadyExists` | Den Namen gibt es in dieser Organisation schon. | Einen anderen wählen; zwei Zeilen „Filiale Nord“ wären in jeder Auswahl eine Falle. |
| `404 Location.NotFound` | Die Kennung gehört zu keinem Ort dieser Organisation. | Kennung aus `GET /v1/locations` nehmen. |
| `422 Validation.Failed` an `timeZone` | Der Wert ist keine Zone der IANA-Datenbank. | `Europe/Berlin` statt `MEZ` oder `+01:00`. |
| `403 Billing.FeatureNotEntitled` beim Anlegen | Die Fähigkeit **Standorte** ist für diese Organisation abgeschaltet. | Beim Betreiber der Plattform freischalten lassen. |
| `409 Conflict.OptimisticLock` beim Ändern | Jemand anderes hat zwischen Lesen und Schreiben geändert. | Neu lesen und mit der neuen Fassung senden. |

Die drei `Location.*`-Codes stehen mit Bedeutung, Auslöser und Status unter [Fehlercodes: Geräte, Wiedergabe, Meldungen, Betrieb](/de/api/error-codes-devices-operations).

## Weiterführendes

- [Gleichzeitige Änderungen: ETag und If-Match](/de/api/etag-and-if-match)
- [Befehle für Ihre Organisation](/de/cli/commands-organization)
- [Handbuch: Standorte](https://szenera.com/de/docs/displays/create-locations)
