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": [[...], ...]}untercontext._tables.<name>, Spalten nach den projizierten Feldern benannt (ein führendesdata.im Pfad entfällt). Regeln und Transformer sehen sie als DuckDB-Tabellectx_<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¶
- Die Session wird mit dem vom Aufrufer gelieferten Kontext (Periode,
Fondsnummer, …) und
context_fetch_report.status = "pending"angelegt;session.context_fetch_startedfeuert. - Der Worker führt die Sources nacheinander aus; eine fehlgeschlagene Source wird protokolliert, die übrigen laufen trotzdem.
- 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. - Der gemischte Kontext wird erneut gegen das Schema validiert.
session.context_fetch_completedodersession.context_fetch_failedfeuert.
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.