{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://raw.githubusercontent.com/api-evangelist/normapi-com/main/json-schema/normapi-com-parse-result-schema.json",
  "title": "ParseResult",
  "x-generated": "2026-10-26",
  "x-method": "derived",
  "x-generator": "derive-json-schema.py",
  "x-source": "openapi/normapi-com-parsing-api-openapi.yml#/components/schemas/ParseResult",
  "type": "object",
  "required": [
    "syntax",
    "documentKind",
    "source",
    "specificationId",
    "businessProcess",
    "profile",
    "invoice",
    "stated",
    "notMapped",
    "validation"
  ],
  "properties": {
    "syntax": {
      "type": "string",
      "enum": [
        "ubl",
        "cii"
      ]
    },
    "documentKind": {
      "type": "string",
      "enum": [
        "invoice",
        "creditNote"
      ],
      "description": "UBL says so by its root element; CII has one root for both, so\nthere the type code (BT-3) decides — 381 and the other credit-note\ncodes of UNTDID 1001.\n"
    },
    "source": {
      "type": "object",
      "required": [
        "kind",
        "attachment"
      ],
      "properties": {
        "kind": {
          "type": "string",
          "enum": [
            "xml",
            "pdf"
          ]
        },
        "attachment": {
          "type": [
            "string",
            "null"
          ],
          "description": "For a PDF, the embedded file the invoice was read from."
        }
      }
    },
    "specificationId": {
      "type": [
        "string",
        "null"
      ],
      "description": "BT-24, the rules the document declares."
    },
    "businessProcess": {
      "type": [
        "string",
        "null"
      ],
      "description": "BT-23."
    },
    "profile": {
      "type": [
        "string",
        "null"
      ],
      "enum": [
        "xrechnung",
        "en16931",
        null
      ],
      "description": "The `profile` of `POST /v1/invoices` that declares exactly this\nBT-24 — what to pass to generate the invoice again under the same\nrules. Null for any other declaration (XRechnung 2.x, a ZUGFeRD\nBASIC or EXTENDED profile …).\n"
    },
    "invoice": {
      "type": "object",
      "description": "The content in the shape of a `POST /v1/invoices` body — the\nfields of `InvoiceRequest`, nested the same way — with that\nendpoint's defaults applied (`typeCode` 380, empty lists). Every\nfield is present, and a value the document does not have is null,\nrequired fields included: so it is declared here as an object\nrather than as `InvoiceRequest`, whose fields are not nullable. An\nincomplete incoming invoice yields an `invoice` that generation\nrefuses with a 400 naming the gap; a complete one can be sent back\nas it is. Amounts are JSON numbers with the document's decimals.\nWhere EN 16931 states a value once and the request carries it on\neach member, it is copied to each: a VAT group's exemption reason\n(BT-120) to every line, allowance and charge of that group. An\nallowance or charge stated with a percentage has `percent` and\n`baseAmount` and a null `amount` — its stated amount is in\n`stated`.\n"
    },
    "stated": {
      "$ref": "#/$defs/StatedAmounts"
    },
    "notMapped": {
      "type": "array",
      "description": "Every value of the document the response does not carry: each\nelement with text of its own and each attribute (namespace\ndeclarations and `xsi:` attributes aside). A value is listed when\nthe request shape has no place for it (an item classification, the\npayee), when it cannot be read as what it should be (a date that\nis no date, an amount in another currency than the invoice's), or\nwhen the document repeats something the request holds once — the\nfirst is read, the rest listed. Paths are written with the\nprefixes the specifications use, without positions, once each in\ndocument order. Empty for any invoice NormAPI generated.\n",
      "items": {
        "$ref": "#/$defs/NotMapped"
      }
    },
    "validation": {
      "description": "With `validate=true`, the verdict exactly as `POST /v1/validate` returns it; otherwise null.",
      "anyOf": [
        {
          "$ref": "#/$defs/ValidationResult"
        },
        {
          "type": "null"
        }
      ]
    }
  },
  "$defs": {
    "Explanation": {
      "type": "object",
      "properties": {
        "meaning": {
          "type": "string",
          "description": "What the rule actually requires, in ordinary language."
        },
        "cause": {
          "type": "string",
          "description": "Why an invoice typically trips it."
        },
        "fix": {
          "type": "string",
          "description": "What to change to make it pass."
        },
        "url": {
          "type": "string",
          "format": "uri",
          "description": "Where the rule is explained on the site: its own page, with an XML\nexample for the common rules, or its entry on a rule-group page.\n"
        }
      },
      "required": [
        "meaning",
        "cause",
        "fix",
        "url"
      ]
    },
    "Finding": {
      "type": "object",
      "properties": {
        "code": {
          "type": "string",
          "description": "The rule identifier, e.g. BR-DE-15."
        },
        "severity": {
          "type": "string",
          "enum": [
            "ERROR",
            "WARNING",
            "INFORMATION"
          ],
          "description": "As the official XRechnung configuration sets it for the matched\nscenario, which can differ from the rule's own flag — BR-CL-23 is\nfatal as written and a warning in an XRechnung. That is how it\nmatches the verdict in `acceptable`.\n"
        },
        "origin": {
          "type": "string",
          "enum": [
            "SCHEMA",
            "SCHEMATRON"
          ]
        },
        "text": {
          "type": "string",
          "description": "The official rule text, as the rule set words it. Named `text`, not\n`message` — a client generated from an earlier revision of this file\nlooked for `message` and never found it.\n"
        },
        "location": {
          "type": [
            "string",
            "null"
          ],
          "description": "Where in the document the rule fired, in Clark notation: each step\ncarries its namespace as a `Q{…}` prefix rather than a short form\nlike `cac:`, because a prefix means nothing without the document\nthat declared it.\n"
        },
        "line": {
          "type": [
            "integer",
            "null"
          ],
          "description": "Set for schema findings; null for Schematron ones."
        },
        "column": {
          "type": [
            "integer",
            "null"
          ],
          "description": "Set for schema findings; null for Schematron ones."
        },
        "test": {
          "type": [
            "string",
            "null"
          ],
          "description": "The expression the validator evaluated, where it reports one."
        },
        "explanation": {
          "description": "The rule in plain language, in the language `Accept-Language`\nchose. Always present; null for schema findings (origin SCHEMA).\n",
          "anyOf": [
            {
              "$ref": "#/$defs/Explanation"
            },
            {
              "type": "null"
            }
          ]
        }
      },
      "required": [
        "code",
        "severity",
        "origin",
        "text"
      ]
    },
    "NotMapped": {
      "type": "object",
      "required": [
        "path",
        "count"
      ],
      "properties": {
        "path": {
          "type": "string"
        },
        "count": {
          "type": "integer",
          "minimum": 1,
          "description": "How often a value stood there unmapped — once per line for a line-level element."
        }
      }
    },
    "StatedAdjustment": {
      "type": "object",
      "required": [
        "amount"
      ],
      "properties": {
        "amount": {
          "type": [
            "string",
            "null"
          ],
          "description": "BT-92 / BT-99 / BT-136 / BT-141 as stated — where the request has a\npercentage instead, the amount the document derived from it.\n"
        }
      }
    },
    "StatedAmounts": {
      "type": "object",
      "description": "The amounts the document states that NormAPI computes when it\ngenerates, and so does not take in the request: BG-22, BG-23, each\nline's net and every allowance's and charge's amount. Decimal strings\nexactly as written — `336.9` stays `336.9` — and null where the\ndocument states none. Whether they add up is the rule set's question:\nask with `validate=true`.\n",
      "required": [
        "lineTotal",
        "allowanceTotal",
        "chargeTotal",
        "netTotal",
        "vatTotal",
        "vatAccountingCurrency",
        "vatTotalInAccountingCurrency",
        "grossTotal",
        "prepaidAmount",
        "roundingAmount",
        "amountDue",
        "vatBreakdown",
        "lines",
        "allowances",
        "charges"
      ],
      "properties": {
        "lineTotal": {
          "type": [
            "string",
            "null"
          ],
          "description": "BT-106, the sum of the line nets."
        },
        "allowanceTotal": {
          "type": [
            "string",
            "null"
          ],
          "description": "BT-107."
        },
        "chargeTotal": {
          "type": [
            "string",
            "null"
          ],
          "description": "BT-108."
        },
        "netTotal": {
          "type": [
            "string",
            "null"
          ],
          "description": "BT-109, the total without VAT."
        },
        "vatTotal": {
          "type": [
            "string",
            "null"
          ],
          "description": "BT-110, in the invoice currency."
        },
        "vatAccountingCurrency": {
          "type": [
            "string",
            "null"
          ],
          "description": "BT-6, when VAT is also accounted in another currency."
        },
        "vatTotalInAccountingCurrency": {
          "type": [
            "string",
            "null"
          ],
          "description": "BT-111, the VAT total in that currency."
        },
        "grossTotal": {
          "type": [
            "string",
            "null"
          ],
          "description": "BT-112, the total with VAT."
        },
        "prepaidAmount": {
          "type": [
            "string",
            "null"
          ],
          "description": "BT-113 — also in invoice.prepaidAmount."
        },
        "roundingAmount": {
          "type": [
            "string",
            "null"
          ],
          "description": "BT-114."
        },
        "amountDue": {
          "type": [
            "string",
            "null"
          ],
          "description": "BT-115."
        },
        "vatBreakdown": {
          "type": "array",
          "description": "BG-23, one entry per VAT category and rate, in document order.",
          "items": {
            "type": "object",
            "required": [
              "category",
              "rate",
              "taxableAmount",
              "vatAmount",
              "exemptionReason",
              "exemptionReasonCode"
            ],
            "properties": {
              "category": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "BT-118."
              },
              "rate": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "BT-119."
              },
              "taxableAmount": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "BT-116."
              },
              "vatAmount": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "BT-117."
              },
              "exemptionReason": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "BT-120."
              },
              "exemptionReasonCode": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "BT-121, which the request does not carry."
              }
            }
          }
        },
        "lines": {
          "type": "array",
          "description": "One entry per line of `invoice.lines`, in the same order.",
          "items": {
            "type": "object",
            "required": [
              "netAmount",
              "priceDiscount",
              "allowances",
              "charges"
            ],
            "properties": {
              "netAmount": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "BT-131."
              },
              "priceDiscount": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "BT-147 — the request derives it from listPrice and unitPrice."
              },
              "allowances": {
                "type": "array",
                "description": "One per entry of the line's allowances (BG-27).",
                "items": {
                  "$ref": "#/$defs/StatedAdjustment"
                }
              },
              "charges": {
                "type": "array",
                "description": "One per entry of the line's charges (BG-28).",
                "items": {
                  "$ref": "#/$defs/StatedAdjustment"
                }
              }
            }
          }
        },
        "allowances": {
          "type": "array",
          "description": "One per entry of `invoice.allowances` (BG-20).",
          "items": {
            "$ref": "#/$defs/StatedAdjustment"
          }
        },
        "charges": {
          "type": "array",
          "description": "One per entry of `invoice.charges` (BG-21).",
          "items": {
            "$ref": "#/$defs/StatedAdjustment"
          }
        }
      }
    },
    "ValidationResult": {
      "type": "object",
      "properties": {
        "acceptable": {
          "type": "boolean",
          "description": "True when no ERROR-severity finding remains. The verdict."
        },
        "rulesetVersion": {
          "type": "string"
        },
        "scenario": {
          "type": [
            "string",
            "null"
          ],
          "description": "Which profile was recognised. Null means no rules ran at all — an\nempty findings list then does not mean \"correct\".\n"
        },
        "wellFormed": {
          "type": "boolean",
          "description": "Whether the document parsed as XML at all."
        },
        "schemaValid": {
          "type": "boolean",
          "description": "Whether it satisfied the XSD for its syntax."
        },
        "schematronValid": {
          "type": "boolean",
          "description": "Whether the business rules reported nothing whatsoever. Not a\nverdict: the rule set emits advisories on perfectly valid invoices,\nand any advisory sets this false while acceptable stays true.\n"
        },
        "businessRulesEvaluated": {
          "type": "boolean",
          "description": "False when the document failed the schema first."
        },
        "findings": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/Finding"
          }
        }
      },
      "required": [
        "acceptable",
        "rulesetVersion",
        "wellFormed",
        "schemaValid",
        "schematronValid",
        "businessRulesEvaluated",
        "findings"
      ]
    }
  }
}