# BI-Datensätze (https://docs.akollo.com/de/connections/bi-datasets)



Das Reporting veröffentlicht Datensätze: Tabellen mit abgestimmten Kennzahlen, etwa freigegebene Stunden pro Person und Tag. Ein Business-Intelligence-Werkzeug (Power BI, Tableau, Looker Studio oder ein eigenes Skript) liest sie mit einem API-Schlüssel über die öffentliche API. Die Zahlen entsprechen denen in den Berichten von Akollo. Das Werkzeug liest nur; es ändert nie etwas in Akollo.

## 1. Schlüssel anlegen [#1-schlüssel-anlegen]

<Steps>
  <Step>
    ### API keys öffnen [#api-keys-öffnen]

    Öffnen Sie **Integrations → API keys** (API-Schlüssel).
  </Step>

  <Step>
    ### Schlüssel benennen [#schlüssel-benennen]

    Geben Sie dem Schlüssel einen Namen, der das nutzende Werkzeug erkennen lässt, zum Beispiel „Power BI – Finanz-Workspace“.
  </Step>

  <Step>
    ### Dienstkonto wählen [#dienstkonto-wählen]

    Wählen Sie das **Service account** (Dienstkonto), in dessen Namen das Werkzeug handelt. Das Dienstkonto muss Berichtsdatensätze lesen dürfen.
  </Step>

  <Step>
    ### Berechtigung setzen [#berechtigung-setzen]

    Setzen Sie unter **Scopes** die Berechtigung **reports.dataset** (BI-Datensätze, nur lesend) auf **Read**. Lassen Sie alle anderen Berechtigungen auf **None**, sofern das Werkzeug nicht auch diese Datensätze braucht.
  </Step>

  <Step>
    ### Ablaufdatum und Adressen festlegen [#ablaufdatum-und-adressen-festlegen]

    Legen Sie ein Ablaufdatum fest. Ruft das Werkzeug immer von denselben Adressen auf, tragen Sie diese ein.
  </Step>

  <Step>
    ### Schlüssel kopieren [#schlüssel-kopieren]

    Kopieren Sie den Schlüssel, sobald er angezeigt wird. Er wird nur einmal angezeigt.
  </Step>
</Steps>

Ein Schlüssel mit nur dieser Berechtigung kann weder Projekte, Aufgaben noch Personen lesen und nichts ändern.

## 2. Datensätze auflisten [#2-datensätze-auflisten]

```bash
curl https://<akollo-host>/api/public/v1/datasets \
  -H "Authorization: Bearer <api key>" \
  -H "Akollo-Version: 2026-09-24"
```

Die Antwort listet jeden Datensatz, den der Schlüssel lesen darf, mit seiner Version:

```json
{ "data": [ { "key": "hours.daily", "version": 2 } ] }
```

Die Version steigt, wenn sich die Spalten eines Datensatzes ändern. Eine leere Liste bedeutet, dass Ihre Organisation dem Dienstkonto dieses Schlüssels noch keine Datensätze zugewiesen hat. Bitten Sie Ihre Reporting-Administration um die Freigabe.

## 3. Zeilen seitenweise lesen [#3-zeilen-seitenweise-lesen]

```bash
curl "https://<akollo-host>/api/public/v1/datasets/hours.daily/rows" \
  -H "Authorization: Bearer <api key>" \
  -H "Akollo-Version: 2026-09-24"
```

```json
{
  "data": [ { "person": "…", "date": "2026-09-01", "approved_hours": 7.5 } ],
  "has_more": true,
  "next_cursor": "eyJ…",
  "dataset": { "key": "hours.daily", "version": 2 }
}
```

<Mermaid
  title="Einen Datensatz seitenweise lesen"
  chart="`flowchart LR
A[Erste Seite anfordern] --> B[Zeilen erhalten]
B --> C{has_more ist true?}
C -->|Ja| D[Erneut mit next_cursor anfordern]
D --> B
C -->|Nein| E[Alle Zeilen gelesen]`"
/>

* Jede Zeile ist ein flacher Datensatz. Ein Wert ist Text, eine Zahl, wahr/falsch oder leer.
* Solange `has_more` true ist, fragen Sie erneut mit `?cursor=<next_cursor>` an. Die Antwort enthält außerdem einen `Link`-Header mit `rel="next"`, der dieselbe Adresse enthält. Folgen Sie ihm unverändert.
* Wie viele Zeilen eine Seite enthält, bestimmt das Reporting. Der Feed akzeptiert weder `limit` noch `$top` oder `$select`.
* Um nur einen Teil eines Datensatzes zu lesen, ergänzen Sie `$filter` (siehe unten). Behalten Sie denselben `$filter` auf jeder Seite bei; der `Link`-Header enthält ihn bereits.
* Ein Cursor gehört zu genau einem Datensatz, einer Version, einem `$filter` und dem Schlüssel, der ihn erhalten hat. Ändert sich die Version des Datensatzes während des Blätterns oder senden Sie den Cursor mit einem anderen Schlüssel (etwa nach dem Erneuern des Schlüssels), antwortet der nächste Aufruf mit `400 CURSOR_INVALID`. Beginnen Sie dann wieder mit der ersten Seite.

### Filtern mit `$filter` [#filtern-mit-filter]

`$filter` grenzt die Zeilen ein, die das Reporting dem Schlüssel ohnehin zeigt; es erweitert sie nie. Der Filter ist eine Folge von Vergleichen, verbunden mit `and`:

```
<column> <operator> <value> and <column> <operator> <value> …
```

* Spalten sind die eigenen Spaltennamen des Datensatzes (kleingeschrieben, wie in den Zeilen).
* Operatoren: `eq` (gleich), `ne` (ungleich), `gt`, `ge`, `lt`, `le` (größer, größer oder gleich, kleiner, kleiner oder gleich). Nur Kleinbuchstaben.
* Werte: Text in einfachen Anführungszeichen (ein Anführungszeichen im Text schreiben Sie doppelt: `'O''Brien'`), ganze Zahlen (`600`, `-15`), `true`, `false`, `null` (nur mit `eq` oder `ne`) und Datumsangaben als `YYYY-MM-DD` ohne Anführungszeichen.
* Höchstens 8 Vergleiche und 1 024 Zeichen. `or`, `not`, Klammern und Funktionen werden nicht unterstützt.

Beispiele (URL-kodieren Sie den Wert beim Zusammensetzen der Adresse):

```
$filter=local_date ge 2026-09-01 and local_date lt 2026-10-01
$filter=team_id eq '018f0000-0000-7000-8000-000000000001'
$filter=local_date ge 2026-09-01 and employee_count gt 5
```

Ein Filter, der eine Spalte nennt, die der Datensatz nicht hat, oder eine Spalte mit einem Wert falschen Typs vergleicht, führt zu `400 VALIDATION_FAILED`.

### Wenn ein Aufruf abgelehnt wird [#wenn-ein-aufruf-abgelehnt-wird]

| Antwort                                | Bedeutung                                                                                                            | Was zu tun ist                                                                  |
| -------------------------------------- | -------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- |
| `401`                                  | Der Schlüssel ist unbekannt, abgelaufen oder widerrufen                                                              | Schlüssel neu anlegen oder ersetzen                                             |
| `403 SCOPE_MISSING`                    | Der Schlüssel hat keine Berechtigung für BI-Datensätze, oder das Dienstkonto hat sein Leserecht verloren             | Berechtigungen des Schlüssels und Rolle des Dienstkontos prüfen                 |
| `404 NOT_FOUND`                        | Den Datensatz gibt es nicht, oder dieser Schlüssel darf ihn nicht lesen                                              | Datensatzschlüssel mit der Liste abgleichen                                     |
| `400 VALIDATION_FAILED`                | Ein nicht unterstützter Abfrageparameter wie `limit` oder ein `$filter` außerhalb der obigen Regeln                  | Nur `cursor` und `$filter` senden; Spalten und Werte des Filters prüfen         |
| `400 CURSOR_INVALID`                   | Der Cursor ist beschädigt, wurde mit einem anderen Schlüssel erhalten, oder der Datensatz hat die Version gewechselt | Wieder mit der ersten Seite beginnen                                            |
| `429`                                  | Zu viele Aufrufe in einer Minute                                                                                     | Die in `Retry-After` genannte Zeit abwarten                                     |
| `502 integrations.bi.feed_drift`       | Das Reporting hat Daten in unerwarteter Form geliefert; es wurden keine Zeilen gesendet                              | Später erneut versuchen; bei Wiederholung die Akollo-Administration informieren |
| `503 integrations.bi.feed_unavailable` | Das Reporting ist für Ihre Organisation ausgeschaltet                                                                | Später erneut versuchen                                                         |

## Power BI Desktop [#power-bi-desktop]

Wählen Sie **Get data → Blank query**, öffnen Sie den Advanced Editor und fügen Sie die folgende Abfrage ein. Setzen Sie Host, Datensatzschlüssel und API-Schlüssel ein. Für die geplante Aktualisierung im Power-BI-Dienst lassen Sie den Host wie unten als erstes Argument von `Web.Contents` und den Pfad in `RelativePath` stehen. Setzen Sie die Anmeldeinformation der Datenquelle auf **Anonymous**: Der Schlüssel wird im Header übertragen.

```powerquery
let
    Host = "https://<akollo-host>",
    Dataset = "hours.daily",
    ApiKey = "<api key>",
    GetPage = (cursor as nullable text) =>
        Json.Document(
            Web.Contents(
                Host,
                [
                    RelativePath = "api/public/v1/datasets/" & Dataset & "/rows",
                    Query = if cursor = null then [] else [cursor = cursor],
                    Headers = [Authorization = "Bearer " & ApiKey, #"Akollo-Version" = "2026-09-24"]
                ]
            )
        ),
    Pages = List.Generate(
        () => GetPage(null),
        each _ <> null,
        each if _[has_more] then GetPage(_[next_cursor]) else null
    ),
    Rows = List.Combine(List.Transform(Pages, each _[data])),
    Result = Table.FromRecords(Rows, null, MissingField.UseNull)
in
    Result
```

Wird die Berichtsdatei weitergegeben, legen Sie den Schlüssel als Power-BI-Parameter ab statt im Abfragetext.

## Tableau [#tableau]

Tableau liest den Feed auf einem von zwei Wegen.

* **Web Data Connector.** Eine kleine Connector-Seite ruft dieselben zwei Adressen auf: Sie listet die Datensätze für die Tabellenauswahl und liest die Zeilen seitenweise, indem sie `next_cursor` folgt. Der Schlüssel wird im Authentifizierungsschritt des Connectors eingegeben, nicht in der Seite.
* **Geplanter Extrakt.** Ein Skript liest alle Seiten in eine CSV-Datei (oder über die Hyper API von Tableau in eine Hyper-Datei), und Tableau Server oder Tableau Cloud aktualisiert den Extrakt daraus nach Zeitplan. Zum Beispiel:

```python
import csv, os, requests

host = "https://<akollo-host>"
headers = {"Authorization": f"Bearer {os.environ['AKOLLO_API_KEY']}", "Akollo-Version": "2026-09-24"}
url = f"{host}/api/public/v1/datasets/hours.daily/rows"
rows, cursor = [], None

while True:
    page = requests.get(url, headers=headers, params={"cursor": cursor} if cursor else None, timeout=60)
    page.raise_for_status()
    body = page.json()
    rows.extend(body["data"])
    if not body["has_more"]:
        break
    cursor = body["next_cursor"]

with open("hours_daily.csv", "w", newline="", encoding="utf-8") as file:
    columns = sorted({name for row in rows for name in row})
    writer = csv.DictWriter(file, fieldnames=columns)
    writer.writeheader()
    writer.writerows(rows)
```

Bewahren Sie den Schlüssel in der Umgebung oder einem Secrets-Speicher auf, nie in der Skriptdatei.

## Looker Studio [#looker-studio]

Looker Studio liest den Feed auf einem von zwei Wegen.

* **Community Connector.** Ein Apps-Script-Connector ruft dieselben Adressen auf, fragt im Authentifizierungsschritt nach dem Schlüssel (Typ „Key“) und liest die Zeilen seitenweise, indem er `next_cursor` folgt.
* **Google Sheets nach Zeitplan.** Ein Apps Script in einer Tabelle liest die Zeilen über einen zeitgesteuerten Trigger und schreibt sie in ein Tabellenblatt; Looker Studio nutzt diese Tabelle als Quelle. Zum Beispiel:

```javascript
function pullAkollo() {
  const key = PropertiesService.getScriptProperties().getProperty('AKOLLO_API_KEY');
  const base = 'https://<akollo-host>/api/public/v1/datasets/hours.daily/rows';
  const headers = { Authorization: 'Bearer ' + key, 'Akollo-Version': '2026-09-24' };
  let rows = [];
  let cursor = null;

  do {
    const url = cursor ? base + '?cursor=' + encodeURIComponent(cursor) : base;
    const body = JSON.parse(UrlFetchApp.fetch(url, { headers: headers }).getContentText());
    rows = rows.concat(body.data);
    cursor = body.has_more ? body.next_cursor : null;
  } while (cursor);

  const columns = [...new Set(rows.flatMap((row) => Object.keys(row)))];
  const sheet = SpreadsheetApp.getActive().getSheetByName('hours.daily');
  sheet.clearContents();
  sheet.getRange(1, 1, 1, columns.length).setValues([columns]);
  if (rows.length > 0) {
    sheet
      .getRange(2, 1, rows.length, columns.length)
      .setValues(rows.map((row) => columns.map((name) => row[name] ?? '')));
  }
}
```

Bewahren Sie den Schlüssel in den Skripteigenschaften auf und geben Sie die Tabelle nur für Personen frei, die die Zahlen sehen dürfen.

## Weitere Wege, Daten auszugeben [#weitere-wege-daten-auszugeben]

Neben dem Datensatz-Feed listet **Integrations → Data out** (Datenausgabe) Exportziele: Azure Blob, Power BI und Analytics SQL. Ist der Azure-Blob-Export eingeschaltet, schreibt Akollo jede Datei einmal in den gewählten Container und ändert sie danach nicht; vorhandene Dateien löscht, ändert oder liest es nie. Um Datensätze direkt aus der Datenbank zu lesen, siehe [BI-Werkzeug per SQL anbinden](/de/connections/bi-sql-access).

<Screenshot src="/screens/en/data-out.webp" alt="Reiter Data out mit den Exportzielen Azure Blob, Power BI und Analytics SQL und ausgeschaltetem Azure-Blob-Export" caption="Data out: Exportziele neben dem Datensatz-Feed." />

## Bewährte Vorgehensweise [#bewährte-vorgehensweise]

* Ein Schlüssel pro Werkzeug und pro Workspace, damit sich ein Schlüssel ersetzen lässt, ohne die anderen anzuhalten.
* Aktualisieren Sie so oft, wie sich die Zahlen ändern, nicht alle paar Minuten. Jeder Aufruf zählt gegen das Minutenlimit des Schlüssels und wird als API-Nutzung erfasst.
* Ersetzen Sie einen Schlüssel vor seinem Ablauf: **Integrations → API keys → Roll** hält den bisherigen Schlüssel eine kurze Übergangszeit gültig, während Sie das Werkzeug umstellen.

<Callout type="warn" title="Achtung">
  Legen Sie einen API-Schlüssel nie in einer weitergegebenen Berichtsdatei, einer Skriptdatei oder einer Tabelle ab. Nutzen Sie stattdessen einen Parameter, die Umgebung, einen Secrets-Speicher oder die Skripteigenschaften.
</Callout>

## Häufige Fragen [#häufige-fragen]

<Accordions type="single">
  <Accordion title="Warum ist die Liste der Datensätze leer?">
    Ihre Organisation hat dem Dienstkonto dieses Schlüssels noch keine Datensätze zugewiesen. Bitten Sie Ihre Reporting-Administration um die Freigabe.
  </Accordion>

  <Accordion title="Kann ich festlegen, wie viele Zeilen eine Seite enthält?">
    Nein. Die Seitengröße bestimmt das Reporting. Der Feed akzeptiert weder `limit` noch `$top` oder `$select`.
  </Accordion>

  <Accordion title="Warum erhalte ich mitten im Blättern 400 CURSOR_INVALID?">
    Die Version des Datensatzes hat sich während des Blätterns geändert, der Cursor wurde mit einem anderen Schlüssel gesendet (etwa nach dem Erneuern) oder er ist beschädigt. Beginnen Sie wieder mit der ersten Seite.
  </Accordion>

  <Accordion title="Kann ein BI-Werkzeug etwas in Akollo ändern?">
    Nein. Das Werkzeug liest nur. Ein Schlüssel mit ausschließlich der Berechtigung für BI-Datensätze kann weder Projekte, Aufgaben noch Personen lesen und nichts ändern.
  </Accordion>

  <Accordion title="Sind die Zahlen dieselben wie in den Berichten von Akollo?">
    Ja. Die Datensätze enthalten dieselben Zahlen wie die Berichte von Akollo.
  </Accordion>
</Accordions>

## Verwandte Seiten [#verwandte-seiten]

* [BI-Werkzeug per SQL anbinden](/de/connections/bi-sql-access)
* [API und Schlüssel](/de/connections/api)
* [Berichte](/de/product-guide/reports)
