API und MCP
Portfolixir stellt den unterstützten lokalen Workflow über die JSON-API unter
/api/v1 bereit. Der MCP-Begleitdienst in mcp-server/ ist bewusst dünn:
MCP-Tools rufen ausschließlich die JSON-API auf und greifen nicht direkt auf die
Datenbank zu.
Authentifizierung
API-Anfragen benötigen ein lokales Bearer-Token:
Authorization: Bearer <PORTFOLIXIR_API_TOKEN>
Der MCP-Begleitdienst nutzt PORTFOLIXIR_API_TOKEN, um Portfolixir aufzurufen.
PORTFOLIXIR_MCP_TOKEN ist für den HTTP-Transport erforderlich, damit sich
lokale HTTP-Clients beim Begleitdienst authentifizieren können.
Datenregeln
Alle Antworten nutzen JSON-Umschläge mit entweder data oder errors.
Finanz-Decimals werden als Strings serialisiert, einschließlich Mengen, Preise,
Gebühren, Steuern, Kurs-Schlusswerte und monetärer Summen. Request-Payloads für
diese Werte sollten ebenfalls Strings senden.
DELETE /api/v1/securities/:id ist die Erfolgs-Ausnahme: es liefert
204 No Content mit leerem Body. Clients sollten für diese erfolgreiche
Löschantwort keinen JSON-Body parsen.
Wertpapiere
GET /api/v1/securitieslistet Wertpapiere. Zeilen kommen standardmäßig als schlanke Projektion — die feste Whitelistid,name,ticker_symbol,isin,wkn,currency_code,asset_class— damit Routineabfragen klein bleiben;projection=fullliefert den vollständigen Datensatz (Notizen, Feed-Konfiguration, Attribute, Zeitstempel). Optionale Query-Parameter:query,sort,direction, holding_status (all,heldodernot_held),projection(slim/full) undlimit/offsetzur Paginierung (beides nichtnegative Ganzzahlen). Nutze diese, um große Kataloge zu paginieren, statt die ganze Tabelle auf einmal zu holen.POST /api/v1/securitieslegt ein Wertpapier mit einemsecurity-Objekt an.asset_classist ein stabiler String-Code:equity,etf,fund,government_bond,bond,crypto,commodity,index,other, plus die Zertifikat-/Hebel-Codeswarrant,knock_out,factor_certificate,discount_certificate,bonus_certificate,express_certificate,reverse_convertible. Lass es leer, damit die Klasse beim Lesen aus Name/ISIN/Ticker inferiert wird. Um eine Position aus der Allokations-Steuerbasis (den 100 %) und der Drift-Tabelle herauszuhalten, während sie in den Bewertungssummen und der Performance bleibt — z. B. ein als Wertspeicher gehaltener Bitcoin —, versiehst du sie mit einem Bucket und schließt diesen Bucket aus einer Ansicht aus; lies die Allokation dann unter dieser Ansicht.GET /api/v1/securities/:idliefert ein Wertpapier, einschließlich seineridentifier_aliases— der über den ISIN-Wechsel-Endpunkt unten aufgezeichneten früheren ISINs (jeweils mitid,former_isin,changed_on,note).PATCH /api/v1/securities/:idaktualisiert ein Wertpapier mit einemsecurity-Objekt. Das Booleantreat_quotes_as_raw(Standardfalse) ist die ADR-0028-Notluke für Anbieter, die ihre Historie nach einem Aktiensplit nie rückwirkend anpassen: Mit gesetztem Flag werden die synchronisierten Kurszeilen des Wertpapiers als roh (wie gehandelt) behandelt, sodass die Split-Anpassungsfaktoren auch auf sie wirken.DELETE /api/v1/securities/:idlöscht ein Wertpapier, wenn keine abhängigen Transaktionen oder keine Kurshistorie darauf verweisen; referenzierte Wertpapiere liefern409 Conflict.GET /api/v1/securities/searchdurchsucht konfigurierte Online-Wertpapieranbieter. Query-Parameter:query; optionaltypemitsecurityodercrypto.
ISIN-Wechsel (Identifier-Aliasse)
Wenn eine Kapitalmaßnahme einem bestehenden Wertpapier eine neue ISIN gibt, den Wechsel aufzeichnen, statt die ISIN direkt zu editieren: Die frühere ISIN wird ein journalisierter Alias, und das ISIN-Matching des Imports prüft erst aktuelle ISINs, dann die Aliasse — Re-Importe alter Exporte (frühere ISIN) und neuer Exporte (neue ISIN) treffen so weiter dasselbe Wertpapier, statt ein Duplikat anzulegen (ADR-0029). Eine bloße Umbenennung braucht keinen ISIN-Wechsel — sie ist nur eine Namensänderung.
POST /api/v1/securities/:security_id/isin-changezeichnet den Wechsel mit einemisin_change-Objekt auf: Pflichtfeldnew_isin(normalisiert auf getrimmte Großschreibung), optionalchanged_on(ISO-Datum, Standard heute) undnote. Liefert das aktualisierte Wertpapier einschließlich seineridentifier_aliases. Abgelehnt mit422und benanntem Konflikt, wennnew_isinder aktuellen ISIN entspricht, auf einem anderen Wertpapier live ist oder als frühere ISIN eines anderen Wertpapiers aufgezeichnet ist; ein Wechsel zurück auf eine eigene frühere ISIN verbraucht diesen Alias (ein Revert). Jeder Wertpapier-ISIN-Schreibpfad — Anlegen, Aktualisieren und der Anlege-Pfad des Imports — lehnt symmetrisch eine ISIN ab, die als Alias existiert, und benennt das Alias-Wertpapier.DELETE /api/v1/securities/:security_id/identifier_aliases/:idlöscht einen aufgezeichneten Alias (journalisiert), wenn ein ISIN-Wechsel versehentlich aufgezeichnet wurde; liefert204 No Contentoder404, wenn der Alias nicht zu dem Wertpapier gehört.
Beispiel-Payload für einen ISIN-Wechsel:
{
"isin_change": {
"new_isin": "IE000XZSV718",
"changed_on": "2026-07-01",
"note": "merger rename"
}
}
Beispiel-Payload zum Anlegen:
{
"security": {
"name": "Example ETF",
"ticker_symbol": "EXM",
"currency_code": "EUR"
}
}
Kurse
GET /api/v1/securities/:security_id/quoteslistet die Kurshistorie eines Wertpapiers. Optionale Query-Parameter:fromundto, als ISO-Daten formatiert. Ungültige Datumsfilter liefern422 Unprocessable Entitymit Feldfehlern. Jede Zeile beschreibt ihren Split-Status selbst (ADR-0028):closeist der gespeicherte Wert (wird nie verändert),adjusted_closeder split-bereinigte Anzeigewert,basisdie Speicherbasis der Zeile (rawfür wie gehandelt erfasste manuelle Zeilen,provider_mirrorfür rückwirkend angepasste Sync-Zeilen) undadjusted, ob ein Split-Faktor angewendet wurde. Charts und Bewertungen nutzenadjusted_close; Audits prüfen gegenclose. Ein Wertpapier, dessen Anbieter nie rückwirkend anpasst, lässt sich mittreat_quotes_as_rawmarkieren (siehe Wertpapiere), was die Roh-Basis für seine synchronisierten Zeilen erzwingt.PUT /api/v1/securities/:security_id/quotesführt manuelle Kurszeilen ein (Upsert).POST /api/v1/securities/:security_id/sync_quoteslöst die Kurssynchronisierung eines Wertpapiers aus. Die Antwort enthältstatus(ok,skippedodererror); übersprungene und Fehler-Antworten können einenreasonwiemissing_tickeroderno_provider_adapterenthalten.
Beispiel-Payload für Kurs-Upsert:
{
"quotes": [
{
"date": "2026-05-15",
"close": "123.45",
"source": "manual"
}
]
}
Beispiel-Antwort für Kurssynchronisierung:
{
"data": {
"status": "skipped",
"reason": "missing_ticker"
}
}
Portfolios und Konten
Portfolio-Writes sind veraltet (ADR-0024) — nur Kompatibilität; nutze Buckets/Ansichten zur Gruppierung. Portfolios wurden zu internen Kompatibilitätsdatensätzen herabgestuft: die UI gruppiert ausschließlich über Buckets und Ansichten, und Depots/Geldkonten brauchen keine
portfolio_idmehr (ein deterministisches internes Standard-Portfolio wird automatisch gebunden).POST /api/v1/portfoliosundPATCH /api/v1/portfolios/:portfolio_idfunktionieren weiter, antworten aber mit dem Response-HeaderDeprecation: true. Sunset-Hinweis: nach zwei Releases ohne externe Portfolio-Writes verschmilzt eine Folge-Story die Datensätze in Buckets und Ansichten (das Exit-Kriterium des ADR) — plane Migrationen aufPOST /api/v1/bucketsundPOST /api/v1/viewsjetzt. Jeder hier geschriebene Datensatz bleibt in der schreibgeschützten Admin-Liste „Portfoliodatensätze (Kompatibilität)“ der UI sichtbar, nichts wird unsichtbar.
GET /api/v1/portfolioslistet Portfolios (Kompatibilitätsdatensätze).POST /api/v1/portfolioslegt ein Portfolio mit einemportfolio-Objekt an. Veraltet — antwortet mitDeprecation: true; bevorzuge Buckets/Ansichten.GET /api/v1/cash_accountslistet Geldkonten. Jedes trägt einenbalance(Decimal-String, in der eigenen Währung des Kontos), der beim Lesen aus dem Ledger abgeleitet wird: Beträge werden als positive Größen gespeichert und der Transaktions-typeimpliziert die Richtung (Einzahlungen, Dividenden, Zinsen, Steuererstattungen und Verkäufe fügen Cash hinzu; Entnahmen, Gebühren, Steuern und Käufe entfernen es; eine Geldübertragung belastet ihr Konto und schreibt dem Gegenkonto gut). Einbalance_adjustment-Snapshot (siehe unten) verankert den Saldo an einem genannten absoluten Betrag zu seinem Datum, wonach nur spätere Buchungen ihn anpassen.POST /api/v1/cash_accounts/:id/balanceerfasst einen absoluten Saldo-Snapshot für ein Konto (ADR-0009): den aktuellen Saldo zu einem Datum, statt jede Buchung zu spiegeln. Body{"date": "2026-06-01", "amount": "4250.00"}(notesoptional);amountist ein Decimal-String und darf negativ sein (ein Überziehungskredit). Es speichert einebalance_adjustment-Transaktion und gibt sie zurück. Der Saldo verankert sich dann an diesem Betrag, und nur Buchungen mit einem Datum strikt nach dem Snapshot verändern ihn, sodass Geld zwischen deinen eigenen Konten zu verschieben keine Übertragungsbuchung braucht. Unbekannte Konten liefern404 Not Found.POST /api/v1/cash_accountslegt ein Geldkonto mit einemcash_account-Objekt an.portfolio_idist optional (ADR-0024): fehlt sie, wird das Konto an das deterministische interne Standard-Portfolio gebunden; eine explizite id gewinnt weiterhin (Kompatibilität). Das optionaleliquidity_role(Standardfree_cash) klassifiziert das Konto:free_cashist echtes verfügbares Cash;credit_lineist eine Überziehungs-/Lombard-Linie, deren negativer Saldo eine Verbindlichkeit ist und deren ungenutzter Rahmen nie Liquidität ist (sie zählt nie zum verfügbaren Cash, auch nicht mit positivem Saldo — der Typ schlägt das Vorzeichen);reserveist ein sichtbarer, aber ausgeschlossener Topf. Nurfree_cash-Konten mit nicht-negativem Saldo gehen in das verfügbare Cash der Bewertung und ihrecash_quoteein. Ein unbekannter Wert wird mit422 Unprocessable Entityabgelehnt.GET /api/v1/cash_accounts/:idliefert ein Geldkonto.PATCH /api/v1/cash_accounts/:idaktualisiert ein Geldkonto (name,currency_code,notes,liquidity_role);portfolio_idkann nicht geändert werden.DELETE /api/v1/cash_accounts/:idlöscht ein Geldkonto oder liefert409 Conflict, wenn eine Transaktion oder ein Wertpapierkonto noch darauf verweist.GET /api/v1/securities_accountslistet Depots/Wertpapierkonten.POST /api/v1/securities_accountslegt ein Depot/Wertpapierkonto mit einemsecurities_account-Objekt an.portfolio_idist optional (ADR-0024): fehlt sie, wird das Depot an das deterministische interne Standard-Portfolio gebunden.GET /api/v1/securities_accounts/:idliefert ein Wertpapierkonto.PATCH /api/v1/securities_accounts/:idaktualisiert ein Wertpapierkonto (name,notes,cash_account_id);portfolio_idkann nicht geändert werden.DELETE /api/v1/securities_accounts/:idlöscht ein Wertpapierkonto oder liefert409 Conflict, wenn eine Transaktion noch darauf verweist.
Beispiel-Payloads für Konten:
{
"portfolio": {
"name": "Household Portfolio",
"base_currency_code": "EUR"
}
}
{
"cash_account": {
"portfolio_id": 1,
"name": "Settlement EUR",
"currency_code": "EUR"
}
}
{
"securities_account": {
"portfolio_id": 1,
"cash_account_id": 1,
"name": "Main Depot"
}
}
Transaktionen und Bestände
GET /api/v1/transactionslistet Transaktionen. Optionale Filter:from/to(ISO-Daten, inklusive),portfolio_id,security_id,securities_account_id. Ungültige Filter liefern422 Unprocessable Entitymit dem betreffenden Feld.POST /api/v1/transactionslegt eine Transaktion beliebiger buchbarer Art mit einemtransaction-Objekt an (die pro Buchungsart erforderlichen Felder werden serverseitig validiert). Buchungssemantik, die man vor dem ersten Schreiben kennen sollte:gross_amounteiner Dividende ist der NETTO-Geldzufluss auf dem Konto — einbehaltene Steuern gehören intaxes, der Einnahmenbericht rekonstruiert brutto als netto plus einbehaltene Steuer. Eine ohnepriceerfasste Einlieferung (inbound_delivery) geht mit Einstand null in die Kostenbasis ein — der Anschaffungskurs sollte mitgegeben werden, wenn er bekannt ist; eine Auslieferung (outbound_delivery) entnimmt den Einstand zum laufenden Durchschnitt, ihr Kurs ist rein informativ. Bei einer Abgleichdifferenz sollte die fehlende Buchung der richtigen Art nachgetragen werden — Kontostand-Snapshots und unbepreiste Einlieferungen sind letzte Mittel, die Zahlen richtig aussehen lassen und dabei den Einstand verzerren. Beträge sind positive Größen — die Buchungsart bestimmt die Richtung; nurbalance_adjustmentdarf einen negativen (absoluten) Betrag tragen. Ein Wertpapier, das über ein Geldkonto in einer anderen Währung abgerechnet wird (zum Beispiel ein USD-Wertpapier über ein EUR-Konto), wird in der eigenen Währung des Wertpapiers gebucht und trägt die Felder zur währungsübergreifenden Abrechnung:security_amount(Handelsbetrag in der Wertpapierwährung),settlement_amount(im Geldkonto in Kontowährung belasteter oder gutgeschriebener Betrag) undsettlement_fx_rate(Einheiten der Kontowährung je einer Einheit der Wertpapierwährung). Fehlt der Kurs, werden jedoch beide Beträge geliefert, wird er alssettlement_amount / security_amountabgeleitet (der tatsächliche Kurs des Brokers); eine Währungsabweichung ohne Kurs und ohne Beträge zur Ableitung wird abgelehnt. Die Einstandsbasis bleibt in der Wertpapierwährung, sodass die positionsbezogene G/V währungsehrlich ist. Alle drei sind Decimal-Strings und bei Buchungen in gleicher Währungnull.GET /api/v1/transactions/:idliefert eine Transaktion.PATCH /api/v1/transactions/:idaktualisiert eine Transaktion (z. B. um eine falsch importierte Buchung zu korrigieren); die Validierung je Art gilt weiter.DELETE /api/v1/transactions/:idlöscht eine Transaktion. Da Trades und Bestände abgeleitet sind, korrigiert oder entfernt das Korrigieren oder Entfernen der Transaktion auch sie.POST /api/v1/splits/previewzeigt eine Aktiensplit-Buchung (ADR-0028) als Vorschau, ohne etwas zu schreiben. Die Anfrage trägtsecurity_id, das Wirksamkeitsdatumdate(ISO, nicht in der Zukunft) und das Verhältnis als Paar positiver Ganzzahlenratio_numerator/ratio_denominator(10:1vorwärts,1:10Reverse-Split; auf kleinste Terme normalisiert,10:5wird also als2:1gebucht). Die Antwort zeigt je Portfolio mit Bestand die Stückzahl unmittelbar vor und nach dem Wirksamkeitsdatum sowie den resultierenden aktuellen Bestand (alles Decimal-Strings; die Verhältnis-Teile bleiben Ganzzahlen), pluswarnings:effective_date_before_historybedeutet, dass das Wirksamkeitsdatum vor der frühesten erfassten Transaktion des Wertpapiers liegt — die gespeicherten Stückzahlen können bereits post-split sein (der Split-Assistent von Portfolio Performance schreibt die Historie destruktiv um), eine Buchung würde dann doppelt anpassen. Vor dem Buchen die Vorschau prüfen. Die Vorschau zeigt außerdem die gespeicherten Schlusskurse rund um das Wirksamkeitsdatum (quotes_around) und einenquote_basis_check(Fehlklassifikations-Wächter, ADR-0028 §2): ein sichtbarer Sprung deutet auf eine Roh-Serie hin, Kontinuität auf eine bereits angepasste; steht das im Widerspruch zur Klassifikation über diesourceder Zeilen, warnt die Vorschau mitquote_basis_contradiction(für Sync-Serien, die nie rückwirkend anpassen, das Flagtreat_quotes_as_rawdes Wertpapiers setzen statt blind zu buchen), und bei zu wenigen Kursen auf einer Seite meldet sieinsufficient_quotes_to_verify_basis, statt eine saubere Prüfung zu suggerieren.POST /api/v1/splitsbucht den Split: ein Aufruf fächert das Ereignis über alle Portfolios mit Bestand am Wirksamkeitsdatum auf — eine journalisiertesplit-Zeile je Portfolio, atomar eingefügt — und liefert die erzeugten Transaktionen (201, reguläres Transaktionsformat). Ein Portfolio ohne Bestand am Wirksamkeitsdatum erhält keine Zeile. Ein zweiter Split am selben Tag für dasselbe Wertpapier wird mit422abgelehnt und benennt das bestehende Ereignis (ein wiederholter Timeout kann den multiplikativen Effekt nicht verdoppeln); ein Datum in der Zukunft und ein Wertpapier ohne Bestand am Wirksamkeitsdatum werden ebenfalls mit422abgelehnt. Der generische EndpunktPOST /api/v1/transactionslehnt die Artsplitab — diese beiden Routen sind der einzige Schreibpfad für Splits.GET /api/v1/portfolios/:portfolio_id/holdingslistet abgeleitete Bestände eines Portfolios, eine Zeile je (Depot, Wertpapier). Jede Zeile trägtquantity, einen gleitenden Durchschnittavg_costundcost_basis(preisbasiert, sodass Gebühren und Steuern nicht in die Stückkosten einfließen), denlatest_price,market_valueundunrealized_pnl_abs/unrealized_pnl_pctgegen diesen Preis, plussecurity_nameundcurrency_code. Alle monetären Größen sind in der eigenen Währung des Wertpapiers (keine FX-Umrechnung — siehe die Bewertung für Summen in Basiswährung); ein Bestand, dessen Wertpapier keinen Kurs hat, liefertnullfür Preis, Marktwert und G/V. Die Antwort ist selbstbeschreibend (FR-13): sie trägtcurrency_basis: "security_currency"(sodass ein Client nie annehmen muss, ob FX angewendet wurde) und einas_of-Datum. Bestände werden beim Lesen abgeleitet, ohne gespeicherten Snapshot, daher istas_ofdas Lesedatum. Unbekannte Portfolios liefern404 Not Found. Optionale Filter:security_id,securities_account_id.GET /api/v1/holdings/by_securityliefert die globale Bewertung je Wertpapier über alle Portfolios hinweg: eineholdings-Zeile je aktuell gehaltenem Wertpapier mitsecurity_id(eine Ganzzahl), Gesamt-quantityund aktuellemmarket_value, umgerechnet in den EUR-Hub, plus einvalued-Flag.valuedistfalse(undmarket_valueistnull), wenn das Wertpapier weder einen Kurs noch einen Handelspreis hat oder kein Wechselkurspfad nach EUR existiert, sodass ein fehlender Kurs oder Kurs einen Wert nie stillschweigend verfälscht. Die Zeilen sind nachsecurity_idsortiert. Die Antwort ist selbstbeschreibend: eincurrencyauf oberster Ebene mit"EUR", einas_of-Lesedatum (der Bericht wird beim Lesen abgeleitet, daher istas_ofdas heutige Datum, kein gespeicherter Zeitpunkt) und einnote, das die Hub-Umrechnung beschreibt. Dies ist das portfolioübergreifende Gegenstück in Basiswährung zur Bestandsliste eines einzelnen Portfolios (die in der eigenen Währung jedes Wertpapiers ohne FX bleibt); für Summen und Gewichte eines Portfolios nutze stattdessen den Bewertungs-Endpunkt.POST /api/v1/holdings/reconcilevergleicht eine vom Nutzer gelieferte externe Positionsliste (Brokerauszug oder Depotübersicht, clientseitig in Zeilen geparst) mit den aus dem Ledger abgeleiteten Beständen — strikt lesend: die Liste kommt ausschließlich im Request-Body an, wird nie gespeichert oder geloggt, und es werden keine Daten von irgendwoher geholt (ADR-0029 §6, FR-35). Jede Zeile ist{identifier, quantity}mit optionalercurrencyund optionalem festnagelndemsecurity_id;quantitymuss ein kanonischer Dezimal-String mit Punkt sein (alles andere — Komma-Dezimalzahlen, Tausendertrennzeichen, Exponenten — ist ein422, das die Zeile benennt; Locale-Parsing ist Aufgabe des Clients), und eine leererows-Liste ist ein422. Identifier laufen durch dieselbe Identitätsleiter wie der Import: ein ISIN-förmiger String (Format und Prüfziffer) matcht nur über die ISIN-Stufe (aktuelle ISINs zuerst, dann erfasste frühere ISINs —matched_via: "former_isin"); jeder andere String wird gegen WKN, Ticker+Währung und Name+Währung geprüft, mit der Genau-eine-Regel über die Vereinigung dieser Stufen — ein String, der die WKN des einen und den Ticker eines anderen Wertpapiers trifft, landet unterambiguousmit den Kandidaten, nie als stille Wahl, und eine Zeile ohne Währung kann nicht über Ticker oder Name matchen (unmatchedmit Grundcurrency_required). Die Antwort ist selbstbeschreibend (basismitas_of,scopeund einer Delta-Notiz) und liefert:matched-Zeilen (eine je Wertpapier — Zeilen, die auf dasselbe Wertpapier auflösen, werden aggregiert, externe Mengen summiert, die beitragenden Zeilen gelistet) mit dermatched_via-Stufe (isin,former_isin,wkn,ticker,nameoderpinned),ledger_quantity,external_quantityunddelta(extern - Ledger) als exakte Decimal-Strings — Ticker-/Name-Treffer tragenweak_match: trueund den Hinweis, das Wertpapier vor jeder Buchung zu bestätigen —,ambiguous- undunmatched-Zeilen sowiemissing_from_list(gehaltene Ledger-Positionen, die die externe Liste nicht abdeckt). Die eingebetteteguidanceist Teil des Vertrags: eine Differenz wird durch Buchen der fehlenden Transaktion der richtigen Art gelöst; Saldo-Snapshots und unbepreiste Einlieferungen sind letzte Mittel, die die Kostenbasis verzerren. Optionaler Scope:portfolio_idoderview(eine View-Id; sich gegenseitig ausschließend — beide zugleich sind ein422), Standard ist die gesamte Instanz; ein unbekanntes Portfolio oder eine unbekannte View ist ein404.GET /api/v1/portfolios/:portfolio_id/valuationliefert eine Live-Bewertung eines Portfolios: jede gehaltene Position bepreist aus ihrem letzten Kurs-Schlusswert, eintotal_valueund dasweightjeder bewerteten Position (ihr Anteil am Gesamtwert). Der Marktwert jeder Position wird aus gespeicherten Wechselkursen in diebase_currencydes Portfolios (Top-Level-Feld) umgerechnet; je Position zeigtsecurity_currencydie native Währung. Ein Wertpapier ohne jeden Kurs wird mit dem zuletzt eigenen Handelspreis bepreist (price_source: "trade", gezählt im Top-Leveltrade_priced_count); eine bepreiste Position trägtprice_source: "quote". Eine Position mit weder Preis oder ohne Wechselkurspfad zur Basiswährung wird mitvalued: false,price_source: nullundnullfür Marktwert und Gewicht zurückgegeben, sodass ein fehlender Preis oder Kurs den Gesamtwert nie verzerrt. Unbekannte Portfolios liefern404 Not Found. Gewichte sind rohe Anteile (market_value / total_value), ausgegeben in voller Decimal-Präzision; da sie normalisierte Verhältnisse sind, müssen sie sich nicht exakt zu1summieren (für die Anzeige runden). Marktwerte undtotal_valuesind exakt. Die Bewertung trägt auch Cash:cash_balanceslistet jedes Geldkonto (balancein eigener Währung, plusbase_value/valuednach Umrechnung in die Basiswährung, seinliquidity_roleund eindeployable-Flag),total_cashist die Basiswährungssumme der bewerteten Geldkonten (sodass der negative Saldo einer gezogenen Kreditlinie ihn weiterhin mindert), undtotal_with_cashisttotal_value + total_cash.cash_quoteist der Anteil des verfügbaren Cash am Portfolio: verfügbares Cash ist die Summe derfree_cash-Konten mit nicht-negativem Saldo (deployable: true), und die Quote wird berechnet, als gäbe es die anderen Konten nicht (counting_cash / (total_value + counting_cash),0, wenn noch nichts zu bewerten ist) — sodass ein Reserve-Konto oder eine Kreditlinie gelistet und intotal_cashbleibt, ohne je Schein-Liquidität zu melden. Die Antwort liefert außerdemcounting_cash(Decimal-String) — das verfügbare Cash, das in die Quote eingeht — sodass ein Konsument diecash_quoteselbst rekonstruieren kann. Ein Konto, dessen Währung keinen Kurspfad zur Basis hat, wirdvalued: falsegemeldet und austotal_cashausgeschlossen, spiegelnd, wie unbepreisbare Positionen behandelt werden. Die Antwort ist selbstbeschreibend (FR-13): sie trägt einas_of-Datum (das Lesedatum — die Bewertung wird live ohne gespeicherten Snapshot berechnet) und einevaluation_note, die angibt, dass Summen inbase_currencyüber den EUR-Hub vorliegen und dass die je Position geführten Felderprice_sourceundvalueddie Preis-Aktualität anzeigen.GET /api/v1/portfolios/:portfolio_id/performanceliefert die echte zeitgewichtete Rendite (TTWROR) des Portfolios, berechnet auf die Portfolio-Performance-Art: das Portfolio wird täglich bewertet (Kurse am oder vor jedem Tag, zu den Kursen jenes Tages umgerechnet, plus Cash), externe Flüsse — Einzahlungen, Entnahmen, Lieferungen und Saldo-Snapshot-Sprünge — werden neutralisiert, und tägliche Renditen werden geometrisch verkettet (siehe ADR-0010). Optionale Query-Parameter:period(ytd,1y,3y,5y,max— Standardmax; ein unbekannter Zeitraum liefert422 Unprocessable Entity) undseries=true, um die täglichen Punkte aufzunehmen (date,value,flow,cumulative_ttwror). Die Antwort trägtttwror,start_date/end_date,start_value/end_value,net_external_flowsals Decimal-Strings undsuspect_dates— Daten von Buchungen älter als 1970 (Import-Tippfehler), deren Effekte am ersten plausiblen Tag angewendet wurden. Nebenttwrorträgt die Antwort auch die geldgewichtete Renditeirr— die einzelne annualisierte Rate, die die datierten externen Flüsse und den Endwert des Zeitraums auf null abzinst (NPV(r) = Σ cf/(1+r)^(days/365) = 0), die Zahl, die Portfolio Performance neben TTWROR zeigt. Es ist ein Decimal-String odernull, wenn keine Rate existiert (weniger als zwei Flüsse, alle Flüsse mit gleichem Vorzeichen oder der Solver konvergiert nicht). Wertpapiere ohne Kurse werden mit dem zuletzt eigenen Handelspreis bepreist (siehe den Bewertungs-Endpunkt). Unbekannte Portfolios liefern404 Not Found.GET /api/v1/portfolios/:portfolio_id/incomeliefert den retrospektiven Ertragsbericht: die bereits im Ledger gebuchten Dividenden und Zinsen, auf drei Arten aggregiert (keine Prognose — der Dividendenkalender ist eine separate Funktion).annualist eine Liste von Jahren (neueste zuerst), jedes mitmonths(eine Map mit Monatszahl-Schlüsseln"1"–"12", jeweils mitdividendsundinterest) und je Jahrdividends_total,interest_totalundtotal.positionsist die Pro-Position-Tabelle:security_id,security_name,security_currency(die ursprüngliche Buchungswährung),gross,tax(die einbehaltene Steuer, aus den auf der Transaktion gespeicherten TAX-Einheiten der Dividende),net(gross - tax),payment_countundlast_payment.transactionsist das Detail je Transaktion für eine Jahres-Aufschlüsselung (kind,date,year,security_id/security_name,currency, das nativenative_gross/native_tax/native_net, dasgross/tax/netin Basiswährung undconverted). Das Brutto einer Dividende ist ihr Netto-Cash (gross_amount) plus die einbehaltene Steuer; Zinsen tragen keine Quellensteuer. Alle Beträge sind Decimal-Strings in derbase_currencydes Portfolios, umgerechnet über den EUR-Hub zum gespeicherten Kurs des jeweiligen Buchungsdatums (dieselbe Mechanik wie der Bewertungs-Endpunkt), mit beibehaltener ursprünglicher Währung;unconverted_countzählt Buchungen ohne Kurspfad (zur Parität umgerechnet), undconversion_notenennt die Basis. Unbekannte Portfolios liefern404 Not Found. Seit ADR-0020 gehört ein SOLL-Zielplan zu einer Sicht (View): Die Lese-/Schreib-Endpunkte für Ziele akzeptieren ein optionalesview(eine View-id). Wird es weggelassen (oder alsnullgesendet), adressiert es den portfolioweiten Gesamt-Plan — das Verhalten vor Einführung der Views. Eine View trägt ihren eigenen Plan, sodass dieselbe Klassifizierung pro View einen anderen Plan halten kann, ohne dass sich die Pläne übereinander summieren. Ein fehlerhaftesviewliefert422 Unprocessable Entity({"view": ["is invalid"]}) und eine unbekannte View-id liefert404 Not Found— derselbe strukturierte Vertrag wie bei den Analyse-Endpunkten.GET /api/v1/portfolios/:portfolio_id/targetslistet die gespeicherten Zielgewichte eines Portfolios (die SOLL-Seite der Allokation). Optionalesclassification_idschränkt die Liste auf einen Baum ein; optionalesviewwählt den Plan (weggelassen = Gesamt). Unbekannte Portfolios liefern404 Not Found.PUT /api/v1/portfolios/:portfolio_id/targetsführt Zielgewichte für eine Klassifizierung ein (Upsert). Der Body ist{"classification_id": id, "targets": [{"category_id": id, "target_weight": "0.25"}]}und kann ein optionales"view": idtragen, um den Plan dieser View zu schreiben (weggelassen = Gesamt). Jedestarget_weightist ein String-Bruch in[0, 1]; Ziele müssen sich nicht zu1summieren. Nur die übergebenen Kategorien werden geändert. Eine Kategorie aus einem anderen Baum liefert422 Unprocessable Entity, und eine unbekannte Klassifizierung liefert404 Not Found.DELETE /api/v1/portfolios/:portfolio_id/targets/:category_identfernt das Zielgewicht eines Portfolios für eine Kategorie und liefert{deleted}(die Zahl der entfernten Zeilen). Optionalesviewwählt den Plan (weggelassen = Gesamt).GET /api/v1/portfolios/:portfolio_id/allocationliefert die SOLL/IST-Aufschlüsselung für eine Klassifizierung (erforderlicherclassification_id-Query-Parameter; ein fehlender liefert422 Unprocessable Entity). Für jede Kategorie meldet esparent_idunddepth(die Kategorien bilden einen Baum),color,own_market_value(direkt zugeordnete Positionen),market_value(ihr ganzer aufgerollter Teilbaum),actual_weight(der aufgerollte Anteil antotal_value),target_weight,drift_weight(actual_weight - target_weight: positiv = übergewichtet, negativ = untergewichtet; ADR-0023) unddrift_value(die Drift in Basiswährung neu ausgewiesen — wie viel zu verkaufen (positiv) oder zu kaufen (negativ) ist, um das Ziel zu erreichen). Jede Zeile trägt zudemchild_target_sum(Decimal-String): die beratende Summe der Ziele ihrer direkten Kinder, odernull, wenn kein direktes Kind ein Ziel trägt — ein Konsistenzhinweis, den die UI gegen das eigenetarget_weightder Zeile abgleichen kann. Eine einem Kind zugeordnete Position zählt zu diesem Kind und jedem Vorfahren, sodass eine übergeordnete Kategorie mit Ziel gegen ihren Teilbaum verglichen wird, statt 0 % zu zeigen; die Zeilen kommen in Baumreihenfolge zurück (Eltern vor ihren Kindern). Da Eltern ihre Kinder aggregieren, summieren sich dieactual_weight-Werte je Kategorie bewusst nicht über die Ebenen zu 1 — nur die Blätter plusunassignedtun es. Jede Kategorie (undunassigned) trägt außerdempositions: die Aufschlüsselung je Wertpapier ihres eigenen (direkt zugeordneten) Werts —security_id,security_name,quantity,market_value,weight, plus die reinen Anzeige-Hinweise fürs Rebalancing (ADR-0023):drift_value(der proportionale Anteil der Position an der Kategorie-Drift) undrebalance_quantity(indikative Stückzahl, die zum impliziten Stückpreis der Bewertung zu verkaufen (positiv) oder zu kaufen (negativ) wäre; ohne Gebühren-/Steuermodell, nie eine Order). Beide Hinweise sind ohne Plan und fürunassigned-Positionen ohne eigenes Positions-Sollnull. Einträge kommen größte zuerst, Wertpapiere über Depots zusammengeführt; das ist es, was der äußerste Ring des Sunburst rendert. Positions-Soll (ADR-0030 Slice 2a): diepositionseiner Kategorie sind die Vereinigung ihrer gehaltenen Positionen und der Positions-Ziel-Zeilen des aktiven Plans, je Wertpapier zusammengeführt. Jeder Eintrag trägt zusätzlichtarget_weight(sein Positions-Soll,nullohne eines),drift_weight(IST-Gewicht − SOLL-Gewicht, ADR-0023-Vorzeichen) undheld. Ein Eintrag mit eigenem Soll leitetdrift_valueundrebalance_quantityaus dieser eigenen Drift ab statt aus dem Kategorie-Anteil. Eine Position mit Soll > 0 ohne Bestand erscheint mit IST 0 (held: false, Menge/Wert/Gewicht"0") und voller Untergewichts-Drift — „hier muss gekauft werden” — mit ihrer indikativen Stückzahl zum letzten gespeicherten Kurs (nullohne Kurs; keiner wird erfunden);quote_datenennt das Datum dieses Kurses (null, wenn der Hinweis nicht kursbasiert ist).heldheißt Bestand vorhanden: ein gehaltenes Wertpapier ohne ermittelbaren Preis wird nie als ohne Bestand gemeldet (es bleibt auf den Unbewertet-Flächen). Jeder Eintrag trägt zudemstale(true, wenn seine abgelegte Positions-Ziel-Zeile nicht mehr zur aktuellen Kategorie des Wertpapiers passt). Eine Position wird nur ausgeblendet, wenn ihr Soll 0/fehlend ist und ihr Bestand null. Jede Kategorie-Zeile trägt zudemconflict(explizites Gewicht und Positions-Summe weichen ab — die Summe steuert) undhas_stale(eine hier abgelegte Positions-Zeile ist veraltet), und die Aufschlüsselung trägtdeep_target_sum— die Summe der effektiven Ziele auf der obersten gezielten Ebene je Teilbaum, die einetop_level_target_sumvon0über einem tiefer gesteckten Plan erklärt. Gehaltene, aber im Baum nicht zugeordnete Wertpapiere werden inunassignedsummiert; auchunassigned-Einträge tragen ihr Positions-Soll. Gewichte sind Anteile der Steuerbasis: der Gesamtwert der bewerteten Positionen (eingeschränkt durch die aktiveview, sofern angegeben), plus das verfügbare Cash (free_cash-Konten mit nicht-negativem Saldo).total_valueist hier diese Steuerbasis (nicht die volle Bewertung). Die Antwort trägt eincash-Objekt —market_value(das zählende Cash),actual_weight(sein Anteil antotal_value),target_weight(das Cash-Ziel des Plans der aktiven View oder0, wenn nicht gesetzt; siehe die Cash-Ziel-Endpunkte unten),drift_weight(actual_weight - target_weight, ADR-0023),drift_value(in Basiswährung neu ausgewiesen) unddistributed(Boolean) — sodass Cash in derselben Drift-Logik wie die Kategorien gesteuert wird. Ist die aktive Klassifizierung der eingebaute Währungs-Baum, wird das Cash jedes Geldkontos seiner eigenen Währungskategorie zugeordnet statt als eigene Cash-Zeile zu erscheinen (EUR-Cash → EUR-Kategorie, USD-Cash → USD usw.); in diesem Fall istcash.distributedtrueund Konsumenten sollten die separate Cash-Zeile weglassen. Da Cash Teil der 100 %-Basis ist, schrumpfen die Kategorie-Prozentsätze entsprechend, sobald Cash vorhanden ist. Dertop_level_target_sumist die Summe der Ziele der Wurzelkategorien plus das Cash-Ziel (außer im Währungs-Baum, wo Cash in Kategorien verteilt wird), verglichen mit1. Um einen Bestand aus der Steuerbasis herauszuhalten, während er weiterhin zum Gesamtvermögen zählt, versiehst du ihn mit einem Bucket und schließt diesen Bucket aus derviewaus, unter der du die Allokation liest — er fällt dann aus den eingeschränkten Positionen. Seit ADR-0020 spiegelt die SOLL-Seite den Plan der aktiven View wider: Mitview=<id>werden die Zielgewichte, das Cash-Ziel und dertop_level_target_sumdieser View ausgewiesen (ohneviewder Gesamt-Plan), sodass die Drift-Tabelle pro View gegen einen kohärenten 100 %-Plan steuert. Dietarget_weight-Werte der Kategorien sind die effektiven Ziele (ADR-0030): die Summe der Positions-Zeilen einer Kategorie, sobald welche existieren (Positionen sind die Quelle der Wahrheit), sonst ihr explizites Kategorien-Gewicht — die Σ-Werte verwenden dieselben effektiven Zahlen. Für die rohen Positions-Ziel-Zeilen und den Roll-up je Kategorie (die Pflege-Sicht) dient derposition_targets-Endpunkt oben. Unbekannte Portfolios oder Klassifizierungen liefern404 Not Found.GET /api/v1/portfolios/:portfolio_id/riskliefert eine Risiko-/Konzentrationssicht für ein Portfolio über die Steuerbasis (der Gesamtwert der bewerteten Positionen, eingeschränkt durch die aktiveview— dieselbe Basis wie die Allocation-Drift). Ein über mehrere Depots gehaltenes Wertpapier wird zu einer Einzeltitel-Position zusammengeführt. Gewichte, Caps und der HHI liegen alle auf einer 0-100-Prozentskala (Decimal-Strings, volle Präzision, keine Rundung):steerable_basisist die Basis, deren Anteil die Gewichte sind, undbase_currencydie Basiswährung des Portfolios.top_holdingssind die größten Einzeltitel-Positionen, größte zuerst, Standard N = 10 (überschreibbar mit demtop_n-Query-Parameter). Jeder Eintrag trägtsecurity_id,security_name,asset_class,market_value,weightund einenseverity(ok/warn/hard). Derseverityist instrumententyp-abhängig: eine Einzelaktie warnt über7und wird hart über10; ein ETF (die Anlageklasseetf) warnt über25und wird nie hart. Überschreibe die Standardwerte mit den Query-Parameternstock_thresholds[warn]/stock_thresholds[hard]undetf_thresholds[warn].hhiträgt den Herfindahl-Hirschman-Index der Einzeltitel-Gewichte (value= Summe der quadrierten Prozentgewichte, auf der0-10000-Skala) plus einband:low(< 1500),moderate([1500, 2500]) oderconcentrated(> 2500). Überschreibe die Schwellen mithhi_bands[low]undhhi_bands[high].asset_class_violationssind opt-in Anlageklassen-Cap-Verletzungen: es gibt keine voreingestellten Standardwerte, Caps werden pro Aufruf mit dem Query-Parameterasset_class_caps[<asset_class>](ein Prozentwert, z. B.asset_class_caps[equity]=50) konfiguriert. Nur Klassen, deren aktuelles Prozentgewicht den Cap übersteigt, kommen zurück, je mitasset_class,current_weight,capundoverage(aktuell − Cap, in Prozentpunkten).
Die Sicht ist eine reine Lese-Ableitung der Live-Bewertung und der Anlageklassen-Klassifizierung — nichts wird gespeichert, sie ist also deterministisch beim Lesen. Eine ungültige Überschreibung (z. B. ein nicht-positiver
top_n) liefert422 Unprocessable Entity; unbekannte Portfolios liefern404 Not Found.GET /api/v1/portfolios/:portfolio_id/cash_targetliest das Cash-Ziel eines Plans, den SOLL-Cash-Anteil an der 100 %-Basis der Allokation (Wertpapiere + zählendes Cash). Die Antwort ist{"cash_target_weight": "0.05"}(ein String-Bruch in[0, 1]odernull, wenn keines gesteuert wird). Optionalesviewwählt den Plan (weggelassen = der Gesamt-Plan). Unbekannte Portfolios liefern404 Not Found, ein fehlerhaftesviewliefert422und eine unbekannte View-id404.PUT /api/v1/portfolios/:portfolio_id/cash_targetsetzt (oder löscht mitnull) das Cash-Ziel eines Plans. Der Body ist{"cash_target_weight": "0.05"}und kann ein optionales"view": idtragen (weggelassen = Gesamt). Es gibt den gespeicherten Wert zurück. Gewichte außerhalb des Bereichs liefern422 Unprocessable Entity. Das Cash-Ziel speist diecash-Zeile der Allokation und dentop_level_target_sumder adressierten View.PATCH /api/v1/portfolios/:portfolio_idpatcht die Stammdaten eines Portfolios. Veraltet (ADR-0024) — antwortet mitDeprecation: true; nur Kompatibilität, nutze Buckets/Ansichten zur Gruppierung. Der Body ist{"portfolio": {...}}. Umzug des Cash-Ziels (ADR-0020): Das Cash-Ziel ist vom Portfolio-Objekt auf den View-gebundenen SOLL-Plan gewandert und wird über die beidencash_target-Endpunkte oben bedient. Aus Kompatibilitätsgründen stellt das Portfolio-Objekt weiterhincash_target_weightbereit — einen String-Bruch in[0, 1](z. B."0.05"für 5 %) odernull, um die Steuerung einer Cash-Quote zu beenden — und das Patchen liest/schreibt das Cash-Ziel des Gesamt-Plans (viewweggelassen). Ein Client, der nur das alte Feld kennt, funktioniert also unverändert weiter; nutzePUT /cash_target?view=<id>für ein View-spezifisches Cash-Ziel. Gewichte außerhalb des Bereichs liefern422 Unprocessable Entity; unbekannte Portfolios liefern404 Not Found. Dascash_target_weightist auch in den vonGET/POST /api/v1/portfolioszurückgegebenen Portfolio-Objekten enthalten (das Gesamt-Cash-Ziel).GET /api/v1/securities/:security_id/tradesliefert FIFO-gematchte Trades eines Wertpapiers: offene Lots, geschlossene Round-Trips (mit realisiertem G/V und Haltedauer in Tagen) und etwaige verwaiste Verkäufe. Die Antwort ist selbstbeschreibend (FR-13): sie trägtmethod: "fifo", sodass ein Client nie annehmen muss, wie Lots gegen Verkäufe gepaart wurden. Optionalesfrom/to(ISO-Daten) filtert jedes Bein nach seinem eigenen Datum: offene Lots nach Eröffnungsdatum, geschlossene Round-Trips nach Schlussdatum, verwaiste Verkäufe nach Verkaufsdatum.
Wechselkurse
GET /api/v1/exchange_rateslistet gespeicherte Wechselkurse. Kurse werden gegen den EUR-Hub gehalten (1 base_currency = rate quote_currency); andere Paare werden durch Triangulation abgeleitet, undGBX(Pence) wird alsGBP × 100behandelt.POST /api/v1/exchange_rates/syncholt die neuesten Kurse vom konfigurierten Anbieter (standardmäßig die täglichen EZB-Referenzkurse) und liefert{provider, status, upserted}. Ein Anbieterfehler liefert502 Bad Gateway.
Klassifizierungen
Klassifizierungsbäume ordnen Wertpapiere wie Ordner. Integrierte Bäume
(asset_class, currency) werden automatisch abgeleitet und ihre Struktur ist
gesperrt; das Bearbeiten der Struktur eines integrierten Baums liefert
422 Unprocessable Entity. Die Mitgliedschaft des Anlageklassen-Baums
ist jedoch nur eine Sicht auf das asset_class-Feld jedes Wertpapiers: in der UI
kannst du ein Wertpapier zwischen seinen Kategorien ziehen (was dieses Feld
setzt), und derselbe Effekt wird über die API mit PATCH /api/v1/securities/:id
({"security": {"asset_class": "etf"}}) oder dem MCP-Tool securities.update
erzielt. Setze es auf leer/null für „automatisch”, was die Klasse beim Lesen aus
Name/ISIN/Ticker neu inferiert. Der Währungsbaum bleibt intrinsisch und kann nicht
neu zugeordnet werden.
GET /api/v1/classificationslistet jede Klassifizierung als Baum mit ihrencategoriesundassignments({security_id, category_id}). Integrierte Bäume tragenbuilt_in: trueund einenkey.POST /api/v1/classificationslegt eine eigene Klassifizierung aus einemclassification-Objekt an (name, optionalposition,description).PATCH /api/v1/classifications/:idaktualisiert dasclassification-Objekt einer eigenen Klassifizierung (name,position,description— alle optional).DELETE /api/v1/classifications/:idlöscht eine eigene Klassifizierung und kaskadiert ihre Kategorien und Zuordnungen.POST /api/v1/classifications/:classification_id/categoriesfügt einer eigenen Klassifizierung einecategoryhinzu (name, optionalcolor,description,parent_id,position).PATCH /api/v1/classifications/:classification_id/categories/:idpatcht einecategory(name,color,description,parent_id,position— alle optional). Dieclassification_idder Kategorie kann so nicht geändert werden.DELETE /api/v1/classifications/:classification_id/categories/:idlöscht eine Kategorie und kaskadiert ihre Unterkategorien und Zuordnungen.PUT /api/v1/classifications/:classification_id/assignmentsordnet ein Wertpapier einer Kategorie zu (security_id,category_id) und ersetzt jede bestehende Zuordnung dieses Wertpapiers in der Klassifizierung. Die Antwort trägt einenstatusvoncreated,movedoderunchangedplusprevious_category_id.PUT /api/v1/classifications/:classification_id/assignments/bulkordnet viele Wertpapiere in einem Aufruf einer Kategorie zu (category_id,security_ids) und liefert{assigned, category_id, security_ids}.DELETE /api/v1/classifications/:classification_id/assignments/:security_identfernt die Zuordnung eines Wertpapiers aus der Klassifizierung.
Beispiel-Payload für eine Transaktion:
{
"transaction": {
"portfolio_id": 1,
"securities_account_id": 1,
"security_id": 1,
"type": "buy",
"date": "2026-05-15",
"quantity": "10.00000000",
"price": "123.45",
"fees": "1.50",
"taxes": "0",
"currency_code": "EUR"
}
}
Buckets und Views
Buckets sind überlappende Tags, die auf Bestände (Depots, Geldkonten und
einzelne Wertpapier-Positionen) angewendet werden, um Vermögen tag-basiert
einzugrenzen. Views sind benannte, globale Filter über diese Buckets: ein
Bestand passt, wenn er eingeschlossen ist (immer unter include_all, sonst wenn
er einen der Include-Buckets der View trägt) und keinen der Exclude-Buckets der
View trägt — Exclude gewinnt immer. Bucket-Definitions- und
Zuordnungs-Schreibvorgänge werden journalisiert (ADR-0017);
View-Definitions-Schreibvorgänge bewusst nicht (ADR-0018 §5).
GET /api/v1/bucketslistet Buckets (id,name,color).POST /api/v1/bucketslegt einen Bucket aus einembucket-Objekt an (nameerforderlich, optionalescolor). Ein leerer oder doppelter Name ergibt422.GET /api/v1/buckets/:idliefert einen Bucket; unbekannte ids ergeben404.PATCH /api/v1/buckets/:idändertname/coloreines Buckets.DELETE /api/v1/buckets/:idlöscht einen Bucket und entfernt ihn aus jeder Zuordnung und jedem View-Set, Antwort204 No Content.GET /api/v1/viewslistet Views. Jede View trägtinclude_all, das aufgelösteinclude-Set (das Literal"all"unterinclude_all, sonst eine Liste von Bucket-ids) und dieexclude-Liste von Bucket-ids.POST /api/v1/viewslegt eine View aus einemview-Objekt an (nameerforderlich, optionalesinclude_all, Standardtrue).GET /api/v1/views/:idliefert eine View mit ihrem aufgelösten Filter.PATCH /api/v1/views/:idändertname/include_alleiner View.DELETE /api/v1/views/:idlöscht eine View und ihre Bucket-Sets (204).PUT /api/v1/views/:id/bucketsersetzt die Include-/Exclude-Bucket-Sets einer View. Body:{"include": [..], "exclude": [..]}(beide optional, Standard[], Listen von Bucket-ids). Eine fehlerhafte id-Liste ergibt422.PUT /api/v1/securities_accounts/:id/bucketsersetzt das Standard-Bucket-Set eines Depots (die Buckets, die jede Position erbt, sofern nicht überschrieben). Body:{"bucket_ids": [..]}.PUT /api/v1/cash_accounts/:id/bucketsersetzt das Bucket-Set eines Geldkontos. Body:{"bucket_ids": [..]}.PUT /api/v1/securities_accounts/:id/positions/:security_id/bucketssetzt die Positions-Überschreibung für ein Wertpapier in einem Depot. Ein leeresbucket_idsspeichert den explizit-leeren Zustand (bewusst keine Buckets), unterschieden vom Erben des Depot-Standards; die Überschreibung gewinnt immer gegenüber dem Depot-Standard. Die Antwort nennt das aufgelösteoverride(inherit,explicit_emptyoderexplicit) und dieeffective_bucket_ids.DELETE /api/v1/securities_accounts/:id/positions/:security_id/bucketssetzt die Überschreibung zurück, sodass die Position wieder den Depot-Standard erbt.
Die Analyse-Endpunkte akzeptieren einen optionalen view-Query-Parameter (eine
View-id), um das Ergebnis auf die Bestände der View einzugrenzen:
GET /api/v1/portfolios/:portfolio_id/valuation?view=<id>GET /api/v1/portfolios/:portfolio_id/allocation?classification_id=<id>&view=<id>GET /api/v1/portfolios/:portfolio_id/performance?view=<id>GET /api/v1/portfolios/:portfolio_id/risk?view=<id>
Bei gesetztem view spiegelt die Antwort die aktive View als view: {id, name}
wider (FR-13); der Aufruf ohne View ist unverändert und trägt kein view-Feld.
Eine fehlerhafte View-id ergibt 422, eine unbekannte 404. Derselbe
view-Scope (und derselbe 422/404-Vertrag) gilt für die SOLL-Ziel-Endpunkte
— GET/PUT /api/v1/portfolios/:portfolio_id/targets, DELETE
/api/v1/portfolios/:portfolio_id/targets/:category_id und die
Cash-Ziel-Endpunkte GET/PUT /api/v1/portfolios/:portfolio_id/cash_target —,
wo eine View den SOLL-Plan wählt (weggelassen = der Gesamt-Plan). Der Bestände-
Endpunkt (GET /api/v1/portfolios/:portfolio_id/holdings) ist nicht
view-eingegrenzt: er liefert die Roh-Zeilen pro (Depot, Wertpapier) in der
jeweiligen Wertpapierwährung, sodass ein Client das Buckets/Views-Modell selbst
anhand von securities_account_id und security_id jeder Zeile anwenden kann.
Einstellungen
Ein minimaler Schlüssel-Wert-Speicher trägt die nutzerseitigen Voreinstellungen (ADR-0024). Heute gibt es eine: die Standard-Ansicht, mit der Vermögensseite und Übersicht öffnen, wenn in der UI keine Ansicht ausdrücklich gewählt wurde. Finanzielle Decimals kommen hier nicht vor.
GET /api/v1/settings/default_viewliefert die aktuelle Voreinstellung:{"data": {"view_id": null, "view": null}}wenn keine gesetzt ist (die eingebaute Alles-Sicht), sonst die id plus einview: {id, name}-Echo.PUT /api/v1/settings/default_viewsetzt sie. Body:{"view_id": <id>}mit einer existierenden View-id, oder{"view_id": null}zum Zurücksetzen auf Alles. Eine unbekannte View-id liefert404(nichts wird geschrieben); eine fehlerhafteview_idliefert422. Die Antwort entspricht demGET-Format.
Audit-Journal
Jeder finanzielle Schreibvorgang (Anlegen, Ändern, Löschen) wird in einem append-only Audit-Journal in derselben Datenbanktransaktion wie der Schreibvorgang selbst festgehalten, sodass jede Änderung — auch Löschungen — nachvollziehbar und zurechenbar bleibt. Marktdaten-Synchronisierung (Kurse und Wechselkurse) ist betriebliche Datenpflege und wird bewusst nicht journalisiert.
GET /api/v1/journallistet Journal-Einträge, neueste zuerst. Jeder Eintrag trägtactor_type(owner_ui,api_token_rw,api_token_ro,import_session,system_job) und ein optionalesactor_label, dieoperation(create,update,delete,upsert), den betroffenenresource_type/resource_idsowie diebefore/after-Schnappschüsse (Decimal-Werte sind Strings). Optionale Filter:resource_type,resource_id,actor_type,operation,limit(Standard 100, max. 1000) undinclude_scenarios(true, um persistierte Was-wäre-wenn-Schreibvorgänge einzuschließen; standardmäßig nur echte Schreibvorgänge). Die Antwort ist selbstbeschreibend: einmeta-Objekt nennt denas_of-Zeitpunkt, die Sortierungorder(inserted_at:desc,id:desc), die Anzahlcountund die angewandtenfilters.
Das Journal deckt derzeit die Kontexte Catalog/Fx ab (Wertpapier-Stammdaten); die übrigen Schreibkontexte werden nacheinander scharfgeschaltet.
MCP-Tools
Der MCP-Begleitdienst stellt denselben lokalen Kontrakt als Tool-Aufrufe bereit. Decimal-Eingaben in MCP-Schemata sind Strings.
portfolixir.securities.listportfolixir.securities.get— vollständiger Datensatz eines Wertpapiers einschließlich seineridentifier_aliases(aufgezeichnete frühere ISINs).portfolixir.securities.createportfolixir.securities.updateportfolixir.securities.deleteportfolixir.securities.isin_change— zeichnet einen Kapitalmaßnahmen-ISIN-Wechsel auf, damit Importe über die frühere ISIN weiter zuordnen (ADR-0029).portfolixir.securities.delete_isin_alias— journalisiertes Löschen eines aufgezeichneten Früher-ISIN-Alias.portfolixir.securities.search_onlineportfolixir.quotes.syncportfolixir.quotes.listportfolixir.quotes.upsertportfolixir.portfolios.list— veraltet (ADR-0024): die Beschreibung verweist auf Buckets/Ansichten.portfolixir.portfolios.create— veraltet (ADR-0024): nur Kompatibilität; bevorzugeportfolixir.buckets.create/portfolixir.views.create.portfolixir.cash_accounts.listportfolixir.cash_accounts.createportfolixir.cash_accounts.updateportfolixir.cash_accounts.deleteportfolixir.cash_accounts.set_balanceportfolixir.securities_accounts.listportfolixir.securities_accounts.createportfolixir.securities_accounts.updateportfolixir.securities_accounts.deleteportfolixir.transactions.listportfolixir.transactions.createportfolixir.transactions.updateportfolixir.transactions.deleteportfolixir.splits.previewportfolixir.splits.createportfolixir.holdings.listportfolixir.holdings.by_securityportfolixir.holdings.reconcile— rein lesender Vergleich einer eingefügten externen Positionsliste mit dem Ledger; die Tool-Beschreibung lenkt den Agenten darauf, die fehlende Transaktion der richtigen Art zu buchen statt Saldo-Snapshots oder unbepreiste Einlieferungen zu nutzen.portfolixir.portfolios.valuationportfolixir.exchange_rates.listportfolixir.exchange_rates.syncportfolixir.classifications.listportfolixir.classifications.createportfolixir.classifications.categories.createportfolixir.classifications.updateportfolixir.classifications.deleteportfolixir.classifications.categories.updateportfolixir.classifications.categories.deleteportfolixir.classifications.assignportfolixir.classifications.assign_bulkportfolixir.classifications.unassignportfolixir.trades.listportfolixir.targets.listportfolixir.targets.setportfolixir.targets.deleteportfolixir.portfolios.allocationportfolixir.portfolios.riskportfolixir.portfolios.cash_targetportfolixir.portfolios.set_cash_targetportfolixir.portfolios.incomeportfolixir.portfolios.performanceportfolixir.journal.listportfolixir.buckets.listportfolixir.buckets.getportfolixir.buckets.createportfolixir.buckets.updateportfolixir.buckets.deleteportfolixir.views.listportfolixir.views.getportfolixir.views.createportfolixir.views.updateportfolixir.views.deleteportfolixir.views.set_bucketsportfolixir.securities_accounts.set_bucketsportfolixir.cash_accounts.set_bucketsportfolixir.securities_accounts.set_position_bucketsportfolixir.securities_accounts.clear_position_bucketsportfolixir.settings.get_default_viewportfolixir.settings.set_default_view
portfolixir.settings.get_default_view /
portfolixir.settings.set_default_view lesen und setzen die
Standard-Ansicht-Voreinstellung (ADR-0024): eine view_id pinnt eine Ansicht,
null (oder weglassen) setzt auf die eingebaute Alles-Sicht zurück.
Die Tools portfolixir.portfolios.valuation,
portfolixir.portfolios.allocation, portfolixir.portfolios.performance und
portfolixir.portfolios.risk akzeptieren ein optionales view (eine View-id),
das das Ergebnis auf die Bestände der Bucket-View eingrenzt; die Antwort spiegelt
dann die aktive View wider.
Seit ADR-0020 akzeptieren auch die SOLL-Ziel-Tools (portfolixir.targets.list,
portfolixir.targets.set, portfolixir.targets.delete) und die Cash-Ziel-Tools
(portfolixir.portfolios.cash_target zum Lesen,
portfolixir.portfolios.set_cash_target zum Setzen oder Löschen) ein optionales
view (eine View-id), das den SOLL-Plan wählt; ohne view wird der
portfolioweite Gesamt-Plan adressiert. Das Cash-Ziel ist vom Portfolio-Objekt auf
den Plan gewandert, aber portfolixir.portfolios.set_cash_target ohne view
steuert weiterhin das Gesamt-Cash-Ziel und hat damit dieselbe Wirkung wie das
alte Portfolio-Feld cash_target_weight. Alle Cash-Ziele und Zielgewichte werden
als Decimal-Strings ausgegeben und akzeptiert.