Zum Inhalt

BuiltAPI

Holen Sie Stammdaten und Historie aus einem BuiltAPI- Workspace in den Session-Kontext — die Salden des Vormonats für eine Reconciliation-Regel, den Stammsatz eines Fonds, eine Liste gültiger Kostenstellen —, sodass der Lieferant nur Periode und Fonds nennen muss und Expecto den Rest abruft.

Richtung: nur context. Expecto fragt den Workspace bei der Session-Erstellung ab und mischt das Ergebnis als Skalare oder Tabellen in den Session-Kontext, die Transformer, Entity-Regeln und Geschäftsregeln lesen können.

1. Connection anlegen

Settings → Connections → Add Connection, Typ BuiltAPI:

Feld Wert
Base URL Root-URL des BuiltAPI-Gateways, https://… (ein öffentlicher Host — der SSRF-Schutz lehnt private Bereiche ab)
Workspace ID Die Workspace-UUID; wird bei jedem Aufruf als x-builtapi-workspace-id gesendet
Credentials Ein Service-Token mit Lesezugriff auf den Workspace; gesendet als Authorization: Bearer …

Test ruft die Entity-Liste des Workspace mit take: 1 ab — der günstigste Aufruf, der Token und Workspace-Mitgliedschaft prüft. query_error bedeutet hier, dass die Workspace-ID in der Konfiguration fehlt.

POST /api/integration-connections
{
  "name": "BuiltAPI – master data",
  "type": "builtapi",
  "base_url": "https://api.builtapi.example",
  "config": { "workspace_id": "5c1d…" },
  "credentials": "<service token>"
}

2. Context Sources am Projekt deklarieren

Ein Projekt hält bis zu 20 Context Sources. Jede nennt eine Connection, eine Abfrage und was sie erzeugt — genau eine Kontexttabelle oder einen Skalar. Verwaltung unter Project → Context, per PUT /api/projects/{id}/context-sources oder dem MCP-Tool set_project_context_sources:

{
  "sources": [
    {
      "name": "prev_balances",
      "connection_id": "<connection uuid>",
      "query": {
        "kind": "records_search",
        "entity_id": "balances",
        "filter": { "fund": "{{ context.fund_number }}", "period": "{{ context.prev_period }}" },
        "projection": { "account": 1, "closing_balance": 1 }
      },
      "produces": { "table": "prev_balance" }
    },
    {
      "name": "fund_name",
      "connection_id": "<connection uuid>",
      "query": {
        "kind": "records_search",
        "entity_id": "funds",
        "filter": { "number": "{{ context.fund_number }}" },
        "projection": { "name": 1 },
        "take": 1
      },
      "produces": { "scalar": "fund_name" }
    }
  ]
}

PUT ersetzt die gesamte Liste. Source-Namen folgen ^[A-Za-z][A-Za-z0-9_]{0,63}$; erzeugte Ziele müssen über alle Sources des Projekts eindeutig sein.

Abfragearten

records_search — Filter + Projektion über eine Entity:

Schlüssel Bedeutung
entity_id BuiltAPI-Entity-ID (Buchstaben, Ziffern, Bindestriche). Nie templatisiert.
filter Filterobjekt in BuiltAPIs Abfragesyntax. String-Werte dürfen Platzhalter enthalten.
projection Feld → 1 (aufnehmen) oder 0 (ausschließen). Die Spalten der erzeugten Tabelle folgen den aufgenommenen Feldern; eine Skalar-Source muss genau ein Feld projizieren.
take Optionales Gesamt-Zeilenlimit, 1–100 000.

aggregation — eine Mongo-artige Pipeline über eine Entity, für Joins und Gruppierungen:

Schlüssel Bedeutung
entity_id Wie oben.
pipeline Nicht leere Liste von $-Stufen, z. B. $match, $lookup, $group, $project. Schreib- und Server-Code-Stufen ($out, $merge, $function, …) werden beim Speichern abgelehnt.
take Optionales Gesamt-Zeilenlimit.

Paginierung und Limits

Expecto blättert automatisch durch das Ergebnis, 1 000 Zeilen je Anfrage, bis die Entity erschöpft oder take erreicht ist. Ohne take schlägt ein Ergebnis mit mehr als 100 000 Zeilen mit einer expliziten Fehlermeldung fehl, statt stillschweigend abgeschnitten zu werden — Filter eingrenzen oder take setzen. Jede Seite darf höchstens 10 MiB groß sein; jede Anfrage hat 15 s Timeout.

Was eine Source erzeugt

  • Tabelle — {"columns": [...], "rows": [[...], ...]} unter context._tables.<name>, Spalten nach den projizierten Feldern benannt (ein führendes data. im Pfad entfällt). Regeln und Transformer sehen sie als DuckDB-Tabelle ctx_<name>.
  • Skalar — das einzige projizierte Feld des ersten Datensatzes, unter context.<name>.

3. Ins Kontextschema einbinden

Deklarieren Sie jeden erzeugten Skalar bzw. jede Tabelle im Kontextschema des Projekts und markieren Sie sie mit filled_by: "integration", damit Lieferanten sie nicht angeben müssen und die Vorab-Validierung weiß, dass der Wert aus dem Fetch kommt:

{
  "scalars": [
    { "name": "fund_number", "type": "string", "required": true, "filled_by": "org" },
    { "name": "prev_period", "type": "string", "required": true, "filled_by": "org" },
    { "name": "fund_name", "type": "string", "required": true, "filled_by": "integration" }
  ],
  "tables": [
    { "name": "prev_balance", "required": true, "filled_by": "integration", "columns": [...] }
  ]
}

Das Flag required entscheidet auch, was ein leeres Ergebnis bedeutet: Für ein Pflichtziel ist eine leere Suche ein empty_result-Fehler, der den Fetch scheitern lässt; für ein optionales ergibt sie eine leere Tabelle.

4. Testen, bevor Sie sich darauf verlassen

Test query an der Source-Karte (oder POST /api/integration-connections/test-source, MCP test_context_source) führt die Abfrage mit Beispiel-Platzhalterwerten aus und liefert Zeilenzahl, Spalten und die ersten Zeilen — oder den semantischen Fehler —, ohne eine Session anzulegen:

{
  "connection_id": "<uuid>",
  "query": { "kind": "records_search", "entity_id": "funds", "filter": { "number": "{{ context.fund_number }}" }, "projection": { "name": 1 } },
  "produces": { "scalar": "fund_name" },
  "params": { "context.fund_number": "F-1001" }
}

5. Was bei der Session-Erstellung passiert

  1. Die Session wird mit dem vom Aufrufer gelieferten Kontext (Periode, Fondsnummer, …) und context_fetch_report.status = "pending" angelegt; session.context_fetch_started feuert.
  2. Der Worker führt die Sources nacheinander aus; eine fehlgeschlagene Source wird protokolliert, die übrigen laufen trotzdem.
  3. Die Fragmente werden in den Session-Kontext gemischt. Ein abgerufener Wert gewinnt gegenüber einem vom Aufrufer gelieferten; der Report markiert solche Sources mit overwrote_caller_value: true.
  4. Der gemischte Kontext wird erneut gegen das Schema validiert. session.context_fetch_completed oder session.context_fetch_failed feuert.

Solange der Report pending oder failed ist, startet die Pipeline nicht: Der Parse-Job ist mit verständlichen Gründen je Source blockiert. Beheben Sie die Ursache (Connection, Zugangsdaten, Filter) und starten Sie den Fetch mit POST /api/sessions/{id}/context/fetch oder dem MCP-Tool fetch_session_context erneut — erlaubt bis zur Bestätigung der Session. Manuelle Kontextänderungen in der Zwischenzeit bleiben erhalten; abgerufene Schlüssel werden aufgefrischt.

Der Report an der Session (GET /api/sessions/{id} → context_fetch_report):

{
  "status": "failed",
  "fetched_at": "2026-09-15T06:12:41Z",
  "sources": [
    { "name": "prev_balances", "status": "ok", "rows": 412, "overwrote_caller_value": false },
    { "name": "fund_name", "status": "error", "error_kind": "empty_result",
      "message": "No value found in BuiltAPI for scalar source 'fund_name'" }
  ]
}

error_kind ist eines von connection_error, auth_error, query_error, empty_result — siehe die Referenz.

Platzhalter

In einer Abfrage sind nur zwei Namensräume erlaubt: {{ context.<skalar> }} für im Kontextschema deklarierte Skalare sowie {{ session.id }}, {{ session.period }}, {{ session.data_provider_id }}. Ein String, der genau ein Platzhalter ist, behält den Typ des Werts (Zahlen bleiben Zahlen); in längeren Strings eingebettete Platzhalter werden als Text gerendert. Platzhalter werden nur in den JSON-Body eingesetzt, nie in die URL. Details in der Platzhalter-Referenz.