openapi: 3.1.0
info:
  title: Firmenbuch.app API
  version: 0.2.0
  description: >-
    Versionierte API für österreichische Unternehmensdaten aus amtlichen
    Quellen (Firmenbuch des BMJ, GLEIF, Statistik Austria). Jede Antwort nennt
    ihre Quelle mit Abrufzeitpunkt und SHA-256-Prüfsumme.


    Die API unter /api/v1 steht mit einem API-Schlüssel ab dem Team-Tarif zur
    Verfügung. Die Partner-API unter /api/partner/v1 ist für Vertragspartner,
    die Registerdaten in ihrem eigenen Produkt anzeigen: Zugang über einen
    API-Schlüssel mit Scope "partner" (Enterprise-Tarif), mit einem monatlichen
    Kontingent laut Vertrag. Jede Antwort kommt im Umschlag { data } bzw.
    { error: { code, message } }.


    Weitergabe/Attribution: Firmenbuchdaten stammen aus dem
    High-Value-Dataset des Bundesministeriums für Justiz — Quelle: Republik
    Österreich (BMJ), lizenziert unter CC BY 4.0 in Verbindung mit den
    IWG-Nutzungsbedingungen. Der Quellvermerk ist bei jeder Weitergabe an
    Endkunden mitzuführen (Feld quellen[] je Profil). Gesellschafter- und
    Stammkapitalangaben stammen aus dem digitalen Amtsblatt EVI (evi.gv.at) und
    werden je Anfrage einzeln abgerufen; eine Weitergabe als Rohdatensatz ist
    nicht gestattet. Geburtsdaten natürlicher Personen liefert die API nur auf
    ausdrückliche Anforderung (includeBirthDates=1) und speichert sie nicht.
servers:
  - url: https://api.firmenbuch.app
    description: Produktions-API
security:
  - bearerAuth: []
paths:
  /health:
    get:
      summary: Healthcheck
      security: []
      responses:
        '200': { description: Dienst verfügbar }
  /api/v1/sources/firmenbuch/search:
    post:
      summary: Firmensuche im Firmenbuch
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [firmenwortlaut]
              properties:
                firmenwortlaut: { type: string, example: "Red Bull" }
                exakteSuche: { type: boolean, default: false }
                suchbereich: { type: integer, default: 3 }
                gericht: { type: string }
                rechtsform: { type: string }
      responses:
        '200':
          description: Trefferliste ohne Duplikate, mit Quellenangabe
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      retrievedAt: { type: string, format: date-time }
                      checksum: { type: string, description: SHA-256-Prüfsumme der Quelldaten }
                      sourceUrl: { type: string }
                      count: { type: integer }
                      results:
                        type: array
                        items: { $ref: '#/components/schemas/FirmenbuchHit' }
        '400': { $ref: '#/components/responses/InvalidRequest' }
        '502': { $ref: '#/components/responses/UpstreamError' }
        '503': { $ref: '#/components/responses/NotConfigured' }
  /api/v1/sources/firmenbuch/excerpt:
    post:
      summary: Firmenbuch-Kurzinformation
      description: >-
        Liefert die Kurzinformation zum gewünschten Stichtag. Gesellschafter
        einer GmbH sind darin nicht enthalten; sie stehen im Firmenprofil der
        Partner-API.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [fnr, stichtag]
              properties:
                fnr: { type: string, example: "435836k" }
                stichtag: { type: string, format: date }
                umfang: { type: string, enum: [Kurzinformation], default: Kurzinformation }
      responses:
        '200':
          description: 'Strukturierte Kurzinformation (Firma, Funktionen, beteiligte Rechtsträger) mit Quellenangabe; Funktionen natürlicher Personen enthalten optional funktionen[].geburtsdatum (YYYY-MM-DD), sofern die Kurzinformation es ausweist'
        '400': { $ref: '#/components/responses/InvalidRequest' }
        '502': { $ref: '#/components/responses/UpstreamError' }
        '503': { $ref: '#/components/responses/NotConfigured' }
  /api/v1/sources/firmenbuch/documents:
    post:
      summary: Urkundenliste einer Firma
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [fnr]
              properties:
                fnr: { type: string, example: "435836k" }
      responses:
        '200':
          description: Urkunden (Art, Stichtag, Dateiformat, Größe, Schlüssel), neueste zuerst; höchstens 15 Minuten alt
        '400': { $ref: '#/components/responses/InvalidRequest' }
        '502': { $ref: '#/components/responses/UpstreamError' }
        '503': { $ref: '#/components/responses/NotConfigured' }
  /api/v1/sources/firmenbuch/document:
    post:
      summary: Einzelne Urkunde abrufen
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [key]
              properties:
                key: { type: string, example: "435836_5690342600470_000___000_30_38206608_XML" }
      responses:
        '200':
          description: Inhalt als Base64 (PDF oder XML) mit Metadaten und SHA-256-Prüfsumme
        '400': { $ref: '#/components/responses/InvalidRequest' }
        '502': { $ref: '#/components/responses/UpstreamError' }
        '503': { $ref: '#/components/responses/NotConfigured' }
  /api/v1/sources/firmenbuch/financials:
    post:
      summary: Strukturierte Jahresabschlüsse einer Firma
      description: >-
        Liest die elektronisch eingereichten Jahresabschlüsse der letzten bis zu
        sechs Jahre und gliedert sie nach UGB (Bilanz § 224, GuV § 231). Nicht
        gemeldete Posten fehlen; Jahre, für die nur ein PDF vorliegt, sind als
        "unstructured" gekennzeichnet.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [fnr]
              properties:
                fnr: { type: string, example: "435836k" }
      responses:
        '200':
          description: Je Jahr Kennzahlen, alle Posten mit Geschäfts- und Vorjahreswert sowie die zugrunde liegende Urkunde
        '400': { $ref: '#/components/responses/InvalidRequest' }
        '502': { $ref: '#/components/responses/UpstreamError' }
        '503': { $ref: '#/components/responses/NotConfigured' }
  /api/v1/sources/firmenbuch/signals:
    post:
      summary: Frühwarn-Signale einer Firma (regelbasiert, keine Bonitätsauskunft)
      description: >-
        Wendet die Frühwarn-Regeln auf Kurzinformation, Urkundenliste,
        strukturierte Jahresabschlüsse und den Registerverlauf (Quartalsenden der
        letzten 24 Monate) an — Jahresabschluss überfällig bzw. verspätet
        (§ 277 UGB), negatives oder schmelzendes Eigenkapital, Insolvenz-Organ,
        Liquidation, Wechsel in der Geschäftsführung (ab 3 Ein-/Austritten),
        Sitzverlegungen (ab 2) sowie Edikte zur FN (offenkundige
        Zahlungsunfähigkeit § 49a EO, Vermögensverwertung im Insolvenzverfahren
        § 117 IO). Jedes Signal nennt Regeltext und Belege; nicht prüfbare Regeln
        stehen mit Begründung in "pruefungen".
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [fnr]
              properties:
                fnr: { type: string, example: "102097v" }
      responses:
        '200':
          description: "signale[] (key, regel, schwere info|wichtig|kritisch, titel, erklaerung, regelText, belege[]) und pruefungen[] (regel, status geprueft|nicht_pruefbar|ausstehend, grund)"
        '400': { $ref: '#/components/responses/InvalidRequest' }
        '502': { $ref: '#/components/responses/UpstreamError' }
        '503': { $ref: '#/components/responses/NotConfigured' }
  /api/v1/sources/edikte:
    post:
      summary: Aktuell veröffentlichte Edikte zu einer Firmenbuchnummer
      description: >-
        Ediktsdatei (JustizOnline, Republik Österreich vertreten durch das BMJ):
        offenkundige Zahlungsunfähigkeit (OZ, § 49a EO) und Verkäufe bzw.
        Verpachtungen in Insolvenzverfahren (MV, § 117 IO), zugeordnet über die im
        Edikt angegebene FN. Nur juristische Personen; Edikte ohne FN werden nicht
        erkannt. Die Daten sind höchstens 30 Minuten alt.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [fnr]
              properties:
                fnr: { type: string, example: "102097v" }
      responses:
        '200':
          description: "treffer[] (art OZ|MV, id, bekanntmachung, gericht, aktenzeichen, gegenstand) mit retrievedAt und quelle"
        '400': { $ref: '#/components/responses/InvalidRequest' }
        '502': { $ref: '#/components/responses/UpstreamError' }
        '503': { $ref: '#/components/responses/NotConfigured' }
  /api/v1/sources/firmenbuch/registerverlauf:
    post:
      summary: Registerstände zu den Quartalsenden der letzten 24 Monate
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [fnr]
              properties:
                fnr: { type: string, example: "56247t" }
      responses:
        '200':
          description: "punkte[] (datum, geschaeftsfuehrer[], sitz) — Grundlage der Frühwarn-Signale zu Wechseln; Stichtage vor Bestehen der Firma entfallen"
        '400': { $ref: '#/components/responses/InvalidRequest' }
        '502': { $ref: '#/components/responses/UpstreamError' }
        '503': { $ref: '#/components/responses/NotConfigured' }
  /api/v1/sources/firmenbuch/changes:
    post:
      summary: Registerveränderungen in einem Zeitraum
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [von, bis]
              properties:
                von: { type: string, format: date }
                bis: { type: string, format: date }
                gericht: { type: string }
                rechtsform: { type: string }
                artDerVeraenderung: { type: string }
      responses:
        '200': { description: Veränderungen laut Firmenbuch mit Quellenangabe }
        '400': { $ref: '#/components/responses/InvalidRequest' }
        '502': { $ref: '#/components/responses/UpstreamError' }
        '503': { $ref: '#/components/responses/NotConfigured' }
  /api/v1/network/company:
    post:
      summary: Verflechtungsnetz einer Firma
      description: >-
        Kombiniert Konzernbeziehungen aus GLEIF (Mutter- und
        Tochtergesellschaften, nur für LEI-Inhaber), beteiligte Rechtsträger aus
        der Kurzinformation (z. B. Komplementäre) und gemeinsame Organe mit den
        übergebenen Firmen (max. 10). Jede Verbindung nennt ihre Quelle. Ist
        GLEIF nicht erreichbar, steht ein Hinweis in warnings.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [fnr]
              properties:
                fnr: { type: string, example: "56247t" }
                name: { type: string }
                depth: { type: integer, minimum: 1, maximum: 6, default: 1, description: Anzahl der Ebenen ab der gesuchten Firma }
                workspaceFnrs:
                  type: array
                  description: Firmenbuchnummern, mit denen gemeinsame Organe gesucht werden
                  maxItems: 10
                  items: { type: string }
      responses:
        '200':
          description: '{ nodes (mit level & expanded), edges, warnings, retrievedAt, stats } mit Quellenangabe je Verbindung; sehr große Netze werden begrenzt'
        '400': { $ref: '#/components/responses/InvalidRequest' }
        '502': { $ref: '#/components/responses/UpstreamError' }
        '503': { $ref: '#/components/responses/NotConfigured' }
  /api/v1/network/company/stream:
    post:
      summary: Verflechtungsnetz als NDJSON-Stream
      description: >-
        Wie /network/company, liefert aber einen `application/x-ndjson`-Stream:
        je Ebene ein `{type:"graph",data}`-Chunk (kumulierter Graph), je Firma
        ein `{type:"progress",level,done,total,…}`-Chunk, am Ende `{type:"done"}`.
        Wird die Verbindung geschlossen, endet die Berechnung.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [fnr]
              properties:
                fnr: { type: string }
                name: { type: string }
                depth: { type: integer, minimum: 1, maximum: 6, default: 1 }
                workspaceFnrs: { type: array, maxItems: 10, items: { type: string } }
      responses:
        '200': { description: NDJSON-Stream (graph/progress/done/error je Zeile) }
        '400': { $ref: '#/components/responses/InvalidRequest' }
        '502': { $ref: '#/components/responses/UpstreamError' }
        '503': { $ref: '#/components/responses/NotConfigured' }
  /api/partner/v1/companies:
    get:
      summary: Partner-API — Firmensuche
      description: >-
        Sucht nach dem Firmennamen. Gibt es kaum Treffer (sparse = true) und ist
        q mindestens 5 Zeichen lang, wird zusätzlich direkt im Firmenbuch
        gesucht und das Ergebnis ohne Duplikate ergänzt. Maximal 10 Treffer.
      security: [{ bearerAuth: [] }]
      parameters:
        - in: query
          name: q
          required: true
          schema: { type: string, minLength: 3, example: "Autohaus Zwer" }
      responses:
        '200':
          description: Trefferliste
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      sparse: { type: boolean, description: Weniger als 2 Treffer im Datenbestand }
                      results:
                        type: array
                        maxItems: 10
                        items:
                          type: object
                          properties:
                            fnr: { type: string, example: "34041k" }
                            name: { type: string }
                            rechtsform: { type: string }
                            sitz: { type: string }
                            status: { type: string }
        '400': { $ref: '#/components/responses/PartnerInvalidRequest' }
        '401': { $ref: '#/components/responses/PartnerUnauthorized' }
        '403': { $ref: '#/components/responses/PartnerScopeRequired' }
        '429': { $ref: '#/components/responses/PartnerQuotaExceeded' }
  /api/partner/v1/companies/{fnr}:
    get:
      summary: Partner-API — Firmenprofil
      description: >-
        Ein Aufruf liefert Stammdaten (Firmenbuch-Kurzinformation), Organe,
        Gesellschafter (EVI), Stammkapital, die letzten drei Jahresabschlüsse
        samt Kennzahlen und die ÖNACE-Zuordnung (Statistik Austria). Ist EVI
        oder die Urkundenliste nicht erreichbar, kommt das Profil trotzdem; die
        betroffene Quelle trägt in quellen[] ein Feld error. Geburtsdaten nur
        mit includeBirthDates=1.
      security: [{ bearerAuth: [] }]
      parameters:
        - in: path
          name: fnr
          required: true
          schema: { type: string, example: "34041k" }
          description: Firmenbuchnummer; "FN 34041 k" und führende Nullen werden normalisiert.
        - in: query
          name: includeBirthDates
          schema: { type: string, enum: ["1"] }
          description: Liefert Geburtsdaten natürlicher Gesellschafter (öffentliche Registerangabe, wird nicht gespeichert).
      responses:
        '200':
          description: Firmenprofil mit Quellenangabe je Quelle
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: '#/components/schemas/PartnerCompanyProfile' }
        '401': { $ref: '#/components/responses/PartnerUnauthorized' }
        '403': { $ref: '#/components/responses/PartnerScopeRequired' }
        '404': { description: Zur FN liegt kein aktueller Firmenbuchstand vor (NOT_FOUND) }
        '429': { $ref: '#/components/responses/PartnerQuotaExceeded' }
        '502': { $ref: '#/components/responses/UpstreamError' }
        '503': { $ref: '#/components/responses/NotConfigured' }
  /api/partner/v1/companies/{fnr}/documents/{key}:
    get:
      summary: Partner-API — Jahresabschluss als Datei
      description: >-
        Gibt die Urkunde als Datei zurück. Der Schlüssel muss aus der
        Jahresabschlussliste genau dieser Firma stammen, sonst 404. Dokumente über 15 MB werden mit 413
        abgelehnt. Antwortheader: content-disposition (Dateiname aus dem
        Stichtag) und x-source-checksum (SHA-256 des Inhalts).
      security: [{ bearerAuth: [] }]
      parameters:
        - in: path
          name: fnr
          required: true
          schema: { type: string, example: "34041k" }
        - in: path
          name: key
          required: true
          schema: { type: string }
          description: Urkundenschlüssel aus jahresabschluesse[].key des Firmenprofils.
      responses:
        '200':
          description: Dokumentinhalt
          content:
            application/pdf:
              schema: { type: string, format: binary }
            application/octet-stream:
              schema: { type: string, format: binary }
        '401': { $ref: '#/components/responses/PartnerUnauthorized' }
        '403': { $ref: '#/components/responses/PartnerScopeRequired' }
        '404': { description: Kein freigegebener Jahresabschluss unter diesem Schlüssel (NOT_FOUND) }
        '413': { description: Dokument größer als 15 MB (DOCUMENT_TOO_LARGE) }
        '429': { $ref: '#/components/responses/PartnerQuotaExceeded' }
        '502': { $ref: '#/components/responses/UpstreamError' }
components:
  schemas:
    FirmenbuchHit:
      type: object
      properties:
        fnr: { type: string, example: "56247t" }
        status: { type: string, example: "aufrecht" }
        name: { type: string }
        sitz: { type: string }
        rechtsform: { type: string }
        gericht: { type: string }
    PartnerCompanyProfile:
      type: object
      description: >-
        Firmenprofil der Partner-API. Quellen: Firmenbuch (Republik
        Österreich/BMJ, CC BY 4.0 + IWG), EVI (Amtsblatt der Republik
        Österreich, Einzelabruf je Anfrage), Statistik Austria (ÖNACE,
        CC BY 4.0).
      properties:
        fnr: { type: string, example: "34041k" }
        name: { type: string }
        rechtsform: { type: string }
        status: { type: string, description: "Registerstatus, z. B. aufrecht" }
        sitz: { type: string }
        adresse:
          type: object
          properties:
            strasse: { type: string }
            plz: { type: string }
            ort: { type: string }
            land: { type: string, example: "AT" }
        eintragungsdatum: { type: string, description: Datum der Ersteintragung }
        geschaeftszweig: { type: string }
        oenace:
          type: [object, 'null']
          properties:
            code: { type: string, example: "46649" }
            bezeichnung: { type: string }
        organe:
          type: array
          items:
            type: object
            properties:
              funktion: { type: string }
              person: { type: string }
              seit: { type: string }
        gesellschafter:
          type: array
          items:
            type: object
            properties:
              name: { type: string }
              istPerson: { type: boolean }
              anteilProzent: { type: number }
              einlage: { type: number }
              fnr: { type: string }
              geburtsdatum: { type: string, description: Nur bei includeBirthDates=1 }
        stammkapital: { type: number }
        jahresabschluesse:
          type: array
          maxItems: 3
          items:
            type: object
            properties:
              key: { type: string }
              stichtag: { type: string }
              dokumentart: { type: string }
              contentType: { type: string }
              groesse: { type: integer }
        kennzahlen:
          type: array
          maxItems: 3
          items:
            type: object
            properties:
              year: { type: integer }
              revenue: { type: [number, 'null'] }
              netIncome: { type: [number, 'null'] }
              equity: { type: [number, 'null'] }
              totalAssets: { type: [number, 'null'] }
        quellen:
          type: array
          items:
            type: object
            properties:
              name: { type: string }
              url: { type: string }
              retrievedAt: { type: string, format: date-time }
              checksum: { type: string }
              error: { type: string, description: Quelle war beim Abruf nicht verfügbar }
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: >-
        API-Schlüssel als "Authorization: Bearer cbk_…". Für /api/partner/v1
        ist ein Schlüssel mit Scope "partner" erforderlich (Enterprise-Tarif);
        andere Schlüssel erhalten 403 PARTNER_SCOPE_REQUIRED.
  responses:
    PartnerUnauthorized: { description: Kein oder ungültiger API-Schlüssel (UNAUTHORIZED) }
    PartnerScopeRequired: { description: Schlüssel hat nicht den Scope "partner" (PARTNER_SCOPE_REQUIRED) }
    PartnerQuotaExceeded: { description: "Monatliches Partner-Kontingent ausgeschöpft (QUOTA_EXCEEDED, mit used/limit)" }
    PartnerInvalidRequest: { description: Parameter q fehlt oder ist kürzer als 3 Zeichen (INVALID_REQUEST) }
    InvalidRequest: { description: Pflichtfeld fehlt oder Body ist kein gültiges JSON (INVALID_REQUEST) }
    NotConfigured: { description: Datenquelle derzeit nicht verfügbar (SOURCE_NOT_CONFIGURED) }
    UpstreamError: { description: Die amtliche Quelle hat nicht oder fehlerhaft geantwortet (SOURCE_REQUEST_FAILED) }
