# Datenzugriff, Rechte und Bedingungen

Vier Blöcke des Manifests legen offen, was eine App mit Daten tut, wer sie im Team bedienen darf, was sie im Portal aufsperrt und welchen Bedingungen eine Organisation zustimmt: `dataAccess`, `permissions`, `unlocks` mit `connects` und `legal`. Die Tabellen nennen je Block Format, Grenzwerte und die Wirkung im Portal.

> **Typ:** nachschlagen
> **Für wen:** Entwickler, die eine App so beschreiben, dass der Installationsdialog die richtige Auskunft gibt.
> **Voraussetzung:** ein Manifest nach [Das Manifest](/de/apps-sdk/manifest); das SDK `@szenera/apps-sdk` aus dem Quellbaum der Plattform (nicht öffentlich veröffentlicht). Für die beiden Abfragen am Ende genügt ein Ausweis Ihrer Organisation.

## Vier Blöcke, vier Fragen

| Block | Frage der Organisation | Wo das Portal es zeigt |
|---|---|---|
| `dataAccess` | Was tut die App mit meinen Daten — vor der Installation und danach? | Abschnitt **Daten & Zugriff** der Store-Seite und der Installationsdialog |
| `permissions` | Wer in meinem Team darf die App bedienen? | **Rollen & Rechte** — eingeklappt am Ende der Store-Seite, ausgeschrieben im Installationsdialog |
| `unlocks`, `connects` | Was schaltet die App im Portal frei, welches fremde Konto braucht sie? | **Schaltet im Portal frei** |
| `legal` | Welchen Bedingungen stimme ich zu? | **Wichtiger Hinweis** im Installationsdialog, Store-Seite, Fußzeile des Widgets |

Die Blöcke sind bewusst getrennt: `permissions` beschreibt, wer im Team die App **bedienen** darf — nicht, worauf die App **zugreift**. Das Portal sagt es der Organisation bei den Rechten der App: „Sie steuern, wer im Team die App bedienen darf.“ Worauf die App zugreift, steht getrennt unter **Daten & Zugriff**.

## dataAccess

Fehlt der Block, gilt die zurückhaltendste Lesart: Die App liest nichts, ruft nichts ab und behält ihre eigenen Daten beim Deinstallieren. Er ist deshalb nur dort nötig, wo es etwas zu sagen gibt.

| Feld | Format | Bedeutung | Beschriftung im Portal |
|---|---|---|---|
| `reads` | Liste aus `tenant-profile`, `tenant-branding`, `media-library`, `team`, `player-location`, `fleet-locations`, `location-hours`; Vorgabe leer | Bestehende Bestände der Organisation, die die App liest. | **Liest vorhandene Daten** — je Bestand **Stammdaten der Organisation**, **Logo der Organisation**, **Mediathek**, **Team und Konten**, **Standort der Displays**, **Alle Standorte mit Anschrift und Lage**, **Öffnungszeiten der Standorte mit Feiertagen** |
| `writes` | Liste, max. 8 Einträge, je Eintrag `<bestand>:create-<sache>`, ≤ 60 Zeichen; Vorgabe leer | Schreib-Fähigkeiten auf fremde Bestände. Ändern und Löschen gibt es nicht; eine App kann nur beitragen. | **Fügt Daten hinzu** — „Ändern oder Löschen ist ausgeschlossen. Eine App kann nur beitragen.“ |
| `external` | Liste, max. 8 Einträge mit `name` (1–60), `purpose` (1–200) und optional `sends` (≤ 200) | Fremde Dienste, die die App erreicht: wen, wozu, und was dabei hinausgeht. | **Verbindungen nach außen** — „Übertragen wird: …“ oder „Es werden keine Daten Ihrer Organisation übertragen.“ |
| `retention` | `kept` oder `deleted`; Vorgabe `kept` | Verbleib der App-eigenen Daten beim Deinstallieren. | **Beim Deinstallieren** — mit den Namen der `resources`: „Standorte und Dienstpläne bleiben erhalten. Sie können sie danach endgültig löschen lassen.“ oder „Mit der Deinstallation werden … gelöscht.“, ohne `resources` „Diese App hinterlässt keine eigenen Daten.“ |

**Eine fünfte Karte steht im Portal-Block, kommt aber nicht aus diesem Block.** Unter **Daten & Zugriff** führt das Portal vor den vier Karten die **`resources`** des Manifests auf — die eigenen Datenbereiche, die die App für die Organisation anlegt, beschriftet mit **Legt für Sie an** (siehe [Das Manifest](/de/apps-sdk/manifest)). Die Trennung ist gewollt: `resources` sagt, was hinzukommt, `dataAccess` sagt, was mit Vorhandenem geschieht. Die Handlungskarte des Store-Eintrags zählt die Bereiche als **Eigene Daten**.

Zwei Regeln verbinden den Block mit dem Code:

- **Kein Zugang ohne Deklaration.** Nur wenn `reads` einen Bestand nennt, bindet die Plattform das Token `APP_TENANT_DATA` an den Server-Teil (siehe [Server-Teil: Resolver, Fetcher und Jobs](/de/apps-sdk/backend#die-token)). Bezieht eine Klasse das Token, ohne dass das Manifest einen Bestand nennt, bricht der Start mit Klartext ab und nennt die fehlende Zeile. Auch `player-location` bindet das Token: Der Standort DES Displays kommt zwar im Kontext des Widget-Resolvers (siehe unten), ein fest gewählter Standort aber über `location()` dieses Tokens. `location-hours` ohne `player-location` oder `fleet-locations` bricht den Start ebenfalls ab: Öffnungszeiten hängen an einem Standort, den die App dann nicht sähe.
- **Keine erfundene Schreib-Fähigkeit.** Jeder Eintrag unter `writes` muss im Katalog der Plattform stehen. Der Katalog ist heute leer; die Plattform weist damit jede erklärte Schreib-Fähigkeit ab — und das ist die richtige Auskunft: Es gibt keine.

Beispiel aus der Wetter-App: Sie liest die Stammdaten der Organisation (für die Sprache, in der Wetterlage und Wochentage beschriftet werden) und den Standort des Displays und erreicht einen fremden Dienst. Der Ort kommt aus der Widget-Konfiguration, ein fester Ort oder der Standort des Displays.

```ts
dataAccess: {
  reads: ['tenant-profile', 'player-location'],
  external: [
    {
      name: 'OpenWeather',
      purpose: 'Wir holen Wetterlage und Vorhersage für den gewählten Ort oder den Standort des Displays.',
      sends: 'Nur die auf etwa einen Kilometer gerundeten Koordinaten des gewählten Orts oder des Standorts, an dem das Display steht.',
    },
  ],
},
```

Wer eine Adresse abruft, die ein Nutzer im Portal eingetragen hat, beschreibt sie als Dienst mit sprechendem Namen — die RSS-App nennt „Die eingetragene Feed-Adresse“, die Kalender-App „Die eingetragene ICS-Adresse“ — und ruft sie im Server-Teil nur über `APP_GUARDED_FETCH` ab.

### Der Standort des Displays: `player-location`

Eine Szene läuft oft in allen Filialen einer Organisation. Mit `player-location` zeigt ein Widget an jedem Display dessen Ort, etwa das Wetter vor der Tür oder die Ortszeit, ohne dass jemand eine Szene je Filiale pflegt. Der Standort ist das, was die Organisation im Portal am Display als Standort hinterlegt hat.

Er kommt nicht über `APP_TENANT_DATA`, sondern im Kontext des Widget-Resolvers als `ctx.location`, weil nur dort feststeht, für welches Display gerade aufgelöst wird:

| Feld | Inhalt |
|---|---|
| `id` | Kennung des Standorts (`loc_…`), etwa um ihn mit Daten der App zu verbinden |
| `name` | Name des Standorts, wie die Organisation ihn führt („Filiale Altona“) |
| `city`, `countryCode` | Ort und Ländercode (ISO 3166-1, `DE`) der Anschrift, je `null` ohne Anschrift. Straße und Postleitzahl werden nie übergeben. |
| `latitude`, `longitude` | Koordinaten des Standorts, gemeinsam `null` ohne Koordinaten |
| `timeZone` | Zeitzone des Standorts (`Europe/Berlin`) |
| `hours` | nur mit `location-hours`, siehe [Öffnungszeiten](#öffnungszeiten-location-hours) |

Die Koordinaten stammen aus der Geokodierung der Anschrift und sind gebäudegenau. Wer sie an einen fremden Dienst schickt, rundet sie auf das, was der Zweck braucht, und sagt es in `sends` und in den Bedingungen: Die Wetter-App schickt zwei Nachkommastellen, etwa einen Kilometer, dieselbe Zelle wie ihr Cache-Schlüssel.

Drei Zustände muss ein Resolver unterscheiden:

| `ctx.location` | Wann |
|---|---|
| fehlt | Die App nennt `player-location` nicht unter `reads`, oder die Organisation hat dem Bereich mit der installierten Fassung noch nicht zugestimmt. Nur wer liest, bekommt. |
| `null` | Die App darf lesen, aber es gibt keinen Standort: Kein Display ist im Spiel (Vorschau im Editor ohne gewähltes Display) oder das Display hängt an keinem Standort. |
| Objekt | Display mit Standort, auch ohne Anschrift und ohne Koordinaten. Wer einen Ort nachschlägt, prüft `latitude` und behandelt einen Standort ohne Koordinaten wie `null`. |

Kommt `player-location` mit einer neuen Fassung hinzu, verlangt die Fassung wie jeder neue Datenzugriff die Zustimmung der Organisation (**Update wartet**). Bis zur Annahme fehlt `ctx.location`.

Für `null` braucht das Widget einen Ausweg, etwa einen Ersatzort aus seiner Konfiguration oder einen Platzhalter mit Hinweis, nie ein leeres Widget. Das Wetter nimmt den Ersatzort auch für einen Standort ohne Koordinaten.

Einen fest gewählten Standort der eigenen Organisation, etwa einen Ersatz-Standort für Displays ohne Standort, liest der Server-Teil mit `APP_TENANT_DATA.location(tenantId, locationId)` in derselben Form. Eine Kennung einer anderen Organisation ergibt `null`. Die Vorschau im Editor löst für ein bestimmtes Display auf, wenn sie `playerId` mitschickt (`POST /v1/widget-data/preview`). Im Test setzt `mockWidgetResolverContext({ location: mockWidgetLocation() })` aus `@szenera/apps-sdk/testing` einen Standort, `{ location: null }` den leeren Fall.

### Öffnungszeiten: `location-hours`

Öffnungszeiten sind Stammdaten des Standorts. Die Organisation pflegt sie im Portal am Standort (Reiter **Öffnungszeiten**), über `PATCH /v1/locations/{id}` oder das MCP-Werkzeug `set_location_opening_hours`, und jede App, die sie zeigt, liest dieselben. Der Bereich ist von `player-location` getrennt, weil Öffnungszeiten keine Ortsangabe sind: Eine App, die nur den Ort braucht, bekommt sie nicht, und eine eigene Zustimmung bleibt verständlich.

Mit `location-hours` trägt jeder Standort, den die App liest (`ctx.location` und `location()`, mit `fleet-locations` auch `locations()`), zusätzlich `hours`:

| Feld | Inhalt |
|---|---|
| `region` | Bundesland nach ISO 3166-2 (`DE-HH`) oder `null`. Ohne Bundesland gibt es keine Feiertage. |
| `weekly` | Wochenplan, je Wochentag (`mon` … `sun`) die Fenster `{ opens, closes }`. Liegt `closes` nicht nach `opens`, endet das Fenster am Folgetag, gleiche Uhrzeiten heißen 24 Stunden. |
| `specialDays` | die Sondertermine, die den Zeitraum berühren, `{ startDate, endDate, label, closed, windows }` |
| `holidays` | die gesetzlichen Feiertage im Zeitraum, `{ date, id, name }`; `id` ist stabil (`easter-monday`), damit eine Anzeige den Namen in ihrer Sprache nennen kann |
| `from`, `until` | der Zeitraum: 21 Tage ab dem heutigen Tag in der Zone des Standorts |

`hours` fehlt ohne den Bereich oder ohne Zustimmung, `null` heißt: Die App darf lesen, aber am Standort sind keine Zeiten gepflegt. Was an einem Tag gilt, rechnet `openingDayOf` aus `@szenera/contracts/fleet` mit der Rangfolge Sondertermin vor Feiertag (geschlossen) vor Wochenplan. Ändert die Organisation die Zeiten, das Bundesland oder die Zeitzone, baut die Plattform die Displays des Standorts neu. Den Tageswechsel und das Ende eines Fensters trägt die App selbst über `validUntil` ihres Resolvers.

```ts
dataAccess: {
  reads: ['player-location', 'location-hours'],
},
```

Im Test setzt `mockWidgetLocation({ hours: … })` die Zeiten, `mockWidgetLocation({ hours: null })` den Standort ohne Zeiten.

### Alle Standorte: `fleet-locations`

Ein Abruf, der vorher wissen muss, für welche Orte er Daten holt, liest das Verzeichnis der Standorte: `APP_TENANT_DATA.locations(tenantId)` liefert alle Standorte der Organisation nach Namen geordnet, mit voller Anschrift (`street`, `postalCode`, `city`, `countryCode`), Koordinaten und Zeitzone, mit `location-hours` samt `hours`. Ein Resolver darf nicht nach außen, also braucht ein Job die Orte vorab, der Notdienst etwa die Postleitzahlen. Wer die Anschrift an einen fremden Dienst schickt, nennt in `dataAccess.external` genau das, was hinausgeht.

Im Test liefert `createMockAppContainer({ locationsMock: [...] })` die Standorte für `location()` und `locations()`.

## permissions

Die Installation trägt jedes Recht in den Rechte-Katalog der Organisation ein. Zwei Listen, dieselbe Form:

| Liste | Wer sie trägt | `defaultRoles` |
|---|---|---|
| `tenant` | Das Team der Organisation | Rollen, die das Recht mit der Installation erhalten, z. B. `['OWNER', 'MANAGER', 'MEMBER']` |
| `platform` | Der Plattformbetrieb | leer (`[]`) — kein Teammitglied erhält das Recht |

Jeder Eintrag ist ein Quartett:

| Feld | Format | Bedeutung |
|---|---|---|
| `key` | `<ns>.<verb>` oder `<ns>.<resource>.<verb>` (`^[a-z][a-z0-9-]*(\.[a-z][a-z0-9-]*)+$`), z. B. `pharmacies-on-duty.location.read` | Der Schlüssel, den der Server-Teil über `APP_PERMISSION_GUARD` prüft; die Plattform stellt ihm `apps:<id>:` voran. |
| `label` | Text, mindestens 1 Zeichen | Beschriftung im Rechte-Katalog und im Installationsdialog. |
| `description` | Text, mindestens 1 Zeichen | Ein Satz, was das Recht erlaubt. |
| `defaultRoles` | Liste von Rollen | Startbelegung; die Organisation kann sie im Team-Bereich ändern. |

Der Helfer `definePermission(key)` prüft nur das Format eines Schlüssels und gibt ihn unverändert zurück; die mitgelieferten Apps schreiben die Quartette direkt ins Manifest.

**Neue Rechte in einer neuen Fassung** übernimmt die Plattform nicht still: Sobald die Fassung als `stable` veröffentlicht ist, steht die Installation auf **Update wartet**, die Widgets der App lassen sich im Szenen-Editor nicht einfügen, und der Reiter **Update** der App zeigt die hinzugekommenen Rechte mit **Annehmen** und **Ablehnen**. Ein Aufruf, der die App vorher nutzt, scheitert mit `Grant.PendingReacceptance` (HTTP 409); eine Zustimmung zu einer anderen Manifest-Fassung mit `App.ManifestHashMismatch` (HTTP 409).

## unlocks und connects

Eine App muss keine Fläche mitbringen. `unlocks` und `connects` beschreiben den zweiten Sinn einer App: nicht etwas liefern, sondern etwas aufsperren. Die Funktion lebt in der Plattform; die App ist das Tor davor.

| Feld | Format | Katalog der Plattform | Löst Zustimmung aus? |
|---|---|---|---|
| `unlocks` | max. 8 Schlüssel, kleingeschrieben mit Bindestrichen, ≤ 40 Zeichen | `video-walls` (**Leinwände**), `library-places` (**Orte in der Mediathek**) | Nein — die Zeile gibt der Organisation etwas, sie nimmt ihr nichts und liest nichts von ihr. |
| `connects` | max. 4 Schlüssel, kleingeschrieben mit Bindestrichen, ≤ 40 Zeichen | heute `microsoft-graph` | Die Verbindung selbst stellt die Organisation im Portal her; Erwerb, Erneuerung und Widerruf der Vollmacht liegen einmal in der Plattform. Die App sieht weder Vollmacht noch Zugriffs-Wort. |

Ein Schlüssel, der nicht im Katalog steht, lässt die App nicht laden — sonst könnte eine App eine Funktion oder Anbindung versprechen, hinter der nichts steht. Beide Felder werden ausgelassen statt leer gesetzt: Ein leeres Feld verschöbe den kanonischen Hash jeder bestehenden Fassung.

Die beiden Tor-Apps zeigen die Muster: `video-wall` trägt `unlocks: ['video-walls']`, `onedrive` trägt `unlocks: ['library-places']` und `connects: ['microsoft-graph']`. Der Store-Eintrag zeigt die Freischaltung im Reiter **Daten & Zugriff** unter **Schaltet im Portal frei**. Eine App, die nur eine Kernfunktion der Plattform aufsperrt, steht im Store nicht als App: `video-wall` erscheint dort als Kachel **Kernfunktion · Leinwand** (`coreFeatures` der Übersicht) und fehlt in „Meine Apps“.

## legal

| Feld | Pflicht | Bedeutung |
|---|---|---|
| `disclaimerKey` | ja | Schlüssel der Fassung, auf die sich eine Zustimmung bezieht (`<gruppe>.bedingungen.v1`). Eine inhaltliche Änderung des Hinweises hebt ihn (`v1` → `v2`). |
| `disclaimerSummary` | ja, max. 500 Zeichen | Kurzfassung in Klartext; Installationsdialog (**Wichtiger Hinweis**), Store-Eintrag und Widget-Fußzeile zeigen sie unverändert. Sie stellt die Fassung dar, eine neue Formulierung bei gleichem Schlüssel verlangt keine Zustimmung. |
| `termsPath` | nein | Pfad des Volltexts auf der öffentlichen Website, ≤ 200 Zeichen, nur Kleinbuchstaben, Ziffern, Bindestriche und Schrägstriche (`/app-bedingungen/<gruppe>`). Das Portal setzt seine Basis davor und verlinkt ihn als **Vollständige Bedingungen der App**. Ohne `termsPath` zeigt der Dialog allein die Kurzfassung. |
| `requireAcceptanceAt` | Vorgabe `install` | `install`: Zustimmung bei der Installation und danach mit jeder neuen Fassung des Hinweises. `upgrade`: Zustimmung bei jeder neuen Fassung der App, auch bei gleichem Hinweis. `never`: keine Zustimmung. Die mitgelieferten Apps tragen `install`. |

Apps mit gleichem Rechtsthema zeigen auf **dasselbe** Blatt. Die mitgelieferten Apps verwenden sieben Gruppen:

| Gruppe (`termsPath`) | Apps |
|---|---|
| `/app-bedingungen/fremdinhalte` | `webpage`, `youtube`, `stream`, `embed` |
| `/app-bedingungen/eigene-inhalte` | `notice-board`, `ticker`, `qr-code`, `menu-board` |
| `/app-bedingungen/wetter` | `weather` |
| `/app-bedingungen/nachrichten` | `rss`, `tagesschau`, `bbc`, `bild`, `ndr`, `spiegel`, `deutschlandfunk` |
| `/app-bedingungen/verbindungen` | `calendar`, `onedrive` |
| `/app-bedingungen/notdienst` | `pharmacies-on-duty` |
| `/app-bedingungen/oeffnungszeiten` | `opening-hours` |

Ohne `legal` bleiben `clock`, `countdown`, `countup`, `world-clock`, `word-clock` und `video-wall`. Die Öffnungszeiten zeigen zwar eigene Eingaben der Organisation, die Feiertage rechnet aber die Plattform, und dazu sagen ihre Bedingungen etwas.

**Eine neue Fassung der Bedingungen** — ein anderer `disclaimerKey` — macht ein Release zustimmungspflichtig: Sobald die neue Fassung als `stable` veröffentlicht ist, steht die Installation auf **Update wartet**, genau wie bei neuen Rechten. Wer den Hinweis inhaltlich ändert, hebt deshalb den Schlüssel, im selben Arbeitsgang wie Kurzfassung, Volltext und Fassung der App. Eine neu formulierte Kurzfassung bei gleichem Schlüssel übernimmt die Plattform still. Eine Manifest-Änderung ohne Versionssprung erreicht bestehende Installationen nicht.

## Den Stand einer Installation abfragen

Was aus Manifest, Rechten und Bedingungen geworden ist, liest eine Organisation über zwei Routen ihres eigenen Bestands. Beide verlangen einen Ausweis der Organisation, aber **kein eigenes Recht**: Eine gültige Anmeldung genügt, und ein API-Schlüssel erreicht sie unabhängig davon, welche Rechte in seinem `scope` stehen. Ein Betreiber-Ausweis erreicht sie nicht — beide antworten immer über genau eine Organisation und weisen einen Ausweis ohne Organisation mit `403 Permission.Denied` ab.

| Route | Was die Antwort enthält |
|---|---|
| `GET /v1/me/apps` | Unter `apps` je Installation eine Zeile: `grantId` (Kennung `tag_…`), `appId`, `installedVersion`, `installedAt`, dazu die Anzeigefelder der **installierten** Fassung (`displayName`, `tagline`, `iconUrl`, `coverUrl`, `category`, `tier`, `bundleSizeBytes`), die Datenbereiche als `resources` und `sceneCount` — in wie vielen Szenen die App tatsächlich steckt. Drei Zustände stehen nebeneinander: `status` (`active` oder `pending-reacceptance`), `platformStatus` (`active`, `beta`, `disabled`) und `effect` mit `status` (`active`, `locked`) und `reason` (`ok`, `pro-required`, `premium-required`, `booking-required`, `exempt`). Deinstallierte Apps fehlen in der Liste, abgeschaltete und nicht mehr im Paket enthaltene bleiben darin stehen, sie sind Bestand. Name, Einzeiler und Datenbereiche stehen in der Sprache aus `locale` oder `Accept-Language`, wo die Fassung sie trägt. |
| `GET /v1/me/apps/{appId}/installed` | Die Seite der installierten App: `entry` beschreibt die installierte Fassung, `scenes` die Szenen, die sie verwenden, und `history` die Installation in zwei Blöcken. `history.grant` trägt `status`, `installedVersion`, `acceptedManifestHash`, `pendingManifestHash`, eine Ablehnung mit Fassung und Zeitpunkt (`declinedVersion`, `declinedAt`) und die Konten hinter Installation, Zustimmung und Deinstallation. `history.acceptanceHistory` ist die anhängende Liste jeder Zustimmung, neueste zuerst, je Eintrag `manifestHash`, die Fassung mit Kennung und Nummer (`releaseId`, `version`, beide `null`, wenn die Fassung schon vor der Nummer den Katalog verlassen hatte), `acceptedVia` (`manual` oder `auto`), `reasons`, `acceptedAt` und die Listen `permissions` und `resourceTypes`, denen damals zugestimmt wurde. Eine App ohne Installation ergibt `404 App.NotInstalled`. |

`pendingManifestHash` ist die maschinenlesbare Fassung von **Update wartet**: Steht dort ein Wert, verlangt eine neue Fassung die Zustimmung, und `status` meldet `pending-reacceptance`. Bis zur Entscheidung läuft die Installation in ihrer bisherigen Fassung weiter. Welche Installationen darauf warten, sammelt `GET /v1/me/apps/updates` (mit `state: "pending"`, daneben stehen Angebote höherer Fassungen mit `state: "offer"`). Je App liefert `GET /v1/me/apps/{appId}/update` die Entscheidung in Klartext: Fassung von und nach, Gründe, neue und entfallene Rechte und Datenzugriffe, Bedingungen bisher und neu, die Frist und ob Ablehnen noch geht. Angenommen wird mit `POST /v1/me/apps/{appId}/update/accept`, abgelehnt mit `POST /v1/me/apps/{appId}/update/reject`, beide mit dem `manifestHash` der Fassung, auf die das Update führt, und dem Recht `marketplace.app.accept`. Eine Ablehnung gilt bis zur nächsten höheren Fassung.

```bash
curl -sS "$SZENERA_API/v1/me/apps?locale=en-US" \
  -H "Authorization: Bearer $SZENERA_API_KEY"
```

Die Zustimmungs-Historie ist der Beleg, gegen den sich eine geänderte Fassung prüfen lässt: Jeder Eintrag hält fest, welchem Manifest-Hash mit welchen Rechten wann zugestimmt wurde — und ob ein Mensch das tat oder die Plattform es automatisch übernahm, weil sich weder Rechte noch Bedingungen bewegt hatten.

## Was die Organisation sieht

Der Installationsdialog trägt den Untertitel „Prüfen Sie, was die App mit Ihren Daten tut, und bestätigen Sie die Installation.“ Er zeigt den Block **Daten & Zugriff**, führt darunter die Rechte als **Rollen & Rechte** mit ihrer Anzahl, zeigt die Kurzfassung der Bedingungen unter **Wichtiger Hinweis**, verlinkt den Volltext als **Vollständige Bedingungen der App** und verlangt das Häkchen **Ich habe die Rechte und die Bedingungen der App gelesen und stimme ihnen zu.**, bevor **Installieren** wirkt.

Dieselben Angaben stehen schon im Eintrag der App im Store, im Reiter **Daten & Zugriff**. Wer abwägt, ob er die App überhaupt will, soll ihre Bedingungen vor dem Klick lesen können. Der Reiter nennt, welche eigenen Bereiche die App anlegt (aus `resources`), welche Bestände sie liest, was sie hinzufügt, welche Verbindungen sie nach außen hält und was dabei übertragen wird und was beim Deinstallieren mit ihren Daten geschieht. Darunter stehen **Datenschutz** mit den Empfängern aus `dataAccess.external` und die Kurzfassung der Bedingungen mit dem Verweis auf den Volltext, den das Portal aus `legal.termsPath` setzt. Was das Manifest auslässt, sagt das Portal ausdrücklich („Diese App greift auf keine Ihrer bestehenden Daten zu.“), statt die Karte wegzulassen.

Den Eintrag liefert `GET /v1/me/apps/store/{appId}` als `StoreEntryView`, fertig entschieden: die Lage der App für die Organisation, genau eine Handlung und den Grund mit seinen Zahlen. Die Sätze dazu bildet das Portal, gerechnet wird nur im Server.

Die Sicht der Organisation auf Installieren, Rechte und Zustimmung beschreibt das Handbuch: [Apps im Handbuch](https://szenera.com/de/docs/apps).

## Weiterführendes

- [Das Manifest](/de/apps-sdk/manifest) — die vier Blöcke im Zusammenhang aller Felder.
- [Widgets konfigurieren](/de/apps-sdk/configure-widgets) — was der Widget-Katalog über die Konfiguration eines Widgets preisgibt, und wie ein Client sie prüft.
- [Was ist eine App, und wie kommt sie auf den Bildschirm?](/de/apps-sdk/about-apps) — die Zustände einer Installation im Lebensweg.
- [Apps im Handbuch](https://szenera.com/de/docs/apps) — die Sicht der Organisation.
