---
title: "Validate one invoice, as XML or as a PDF with embedded invoice XML"
method: POST
path: "/v2/einvoices/validate"
tags: ["validation"]
---

# Validate one invoice, as XML or as a PDF with embedded invoice XML

`POST /v2/einvoices/validate`

## Request

- Base URL: `https://api.ironfang.com/finance`
- URL: `https://api.ironfang.com/finance/v2/einvoices/validate`
- Auth: one of:
  - none
  - HTTP bearer

**Body.** Send the document itself with `Content-Type`
`application/xml`, `text/xml` or `application/pdf`, choosing with the
query parameters. Or send `multipart/form-data` with one `document`
part (one of those media types) and an optional `options` part
holding a JSON `ValidationOptions` object; query parameters are not
read for multipart requests. The media type is a hint: the content
is inspected, and an XML part holding a PDF (or the reverse) is
`415 unsupported_content_type`. Archives and compressed bodies are
refused the same way.

**Limits.** XML at most 5 MiB (5,242,880 bytes), 64 levels deep,
250,000 elements. PDF at most 20 MiB, 200 pages and 20 embedded
files; the embedded invoice XML at most 5 MiB; 25 MiB of decoded
attachment data in total; 256 MiB of compressed (Flate) content
decoded across the whole PDF. Over a byte limit is `413`; over any other
processing limit is `422 processing_limit`, which is not a verdict on
the invoice. The XML deadline is 10 seconds and the hybrid deadline
30 seconds; exceeding it is indeterminate (`504`).

**Selection.** `ruleset` is `latest` (default) or an exact ruleset
id.

- With an exact id, the release supplies family, variant, syntax,
  document type and scope. Any selector also sent must equal the
  release's value, or the request is `422 selection_conflict`; the
  document must fit the release (`422 unsupported_document_type`,
  `422 family_mismatch`).
- With `latest`, the family is the one requested or, without
  `family`, the one the document declares
  (`selection_method: detected`); an undeclared or unknown family is
  `422 family_undetected`. A requested family the document
  contradicts is `422 family_mismatch`, and the Problem's
  `detected` says what the document declares, so a client can offer
  the right tool; the document is never validated under the wrong
  family. The variant is the one the document declares; a variant
  Ironfang recognises but does not validate is
  `422 unsupported_variant`, never a fallback to a weaker profile.
- `scope` defaults to `xml` for XML and `hybrid_pdf` for a PDF.
  Any other combination is `422 scope_unavailable`. To inspect only
  the XML inside a PDF, extract it explicitly
  (`POST /tools/v2/invoice/extract`) and validate that XML.

**PDF inputs.** An encrypted PDF is `422 encrypted_pdf`. A PDF with
no identifiable embedded invoice XML is `422 no_embedded_invoice`,
and one with more than one plausible invoice XML is
`422 ambiguous_embedded_invoice`: no attachment is ever chosen
arbitrarily. These three are uncharged. Everything wrong with an
identified embedded invoice - its name, relationship, media type,
metadata, PDF/A conformance, or the XML itself - is a finding in a
completed validation.

**Outcome.** `200` is always a verdict: `status: completed`,
`outcome` `valid` or `invalid`, and every check that ran completed.
A deterministic defect makes the outcome `invalid`, and checks it
prevented are `skipped` with a reason. A timeout, an unavailable
validator or an internal failure is a Problem with
`outcome: indeterminate`; known findings are never presented as a
verdict.

**Usage.** One completed validation is one operation, whatever the
scope; the checks inside it are not billed separately. As in V1, a
completed validation is charged once its schema check (`xsd`)
reached a verdict, passed or failed; a document that is not
well-formed XML ends before it and is not charged. Rejected
requests and indeterminate outcomes are not charged. An
authenticated verdict carries `operation_id` and `usage`, and is
retained for 30 days as V1 results are; it is served only through
V2, never through a V1 result, list or report.

**Idempotency.** As V1, with the request identified by the input
bytes (for a PDF, the PDF's own bytes), the selectors and the scope.
A replay returns the original operation and its result, even if
`latest` has since moved.

**Findings.** At most 1,000 inline, ordered by the pipeline's layer
order, then severity (`fatal`, `warning`, `information`), then
`rule_id`, then location, then message. `findings_summary` states
the totals and whether truncation happened.

## Query parameters

- `ruleset` string
- `family` 'peppol-bis-billing-3' | 'xrechnung' | 'zugferd-facturx': An invoice family. `zugferd-facturx` is one family: ZUGFeRD and Factur-X are corresponding release names of the same standard.
- `variant` 'core' | 'extension' | 'cvd' | 'minimum' | 'basic-wl' | 'basic' | 'en16931' | 'extended' | 'xrechnung': A variant or profile within a family. XRechnung: `core`, `extension`, `cvd`. ZUGFeRD / Factur-X: `minimum`, `basic-wl`, `basic`, `en16931`, `extended`, `xrechnung` (the ZUGFeRD XRECHNUNG reference profile). Peppol BIS Billing 3 has none. Recognition is not support: the ruleset list is the only statement of what is validated.
- `document_type` 'invoice' | 'credit_note' | 'auto'
- `scope` 'xml' | 'hybrid_pdf'

## Headers

- `Idempotency-Key` string

## Response `200`

A verdict within the reported scope.

- ValidationVerdict: The result of one completed validation. `outcome` is decided by the fatal findings of the checks that ran, within `coverage`.
  - `counts` object, required
    - `by_severity` SeverityCounts, required
      - `fatal` integer, required
      - `information` integer, required
      - `warning` integer, required
  - `coverage` Coverage, required: What the result covers. `checked` is the layers that ran to completion; `not_checked` names every aspect the result does not establish, and why.
    - `checked` Layer[], required
    - `not_checked` object[], required
      - `aspect` 'pdf_container' | 'visual_consistency' | 'digital_signature' | 'en16931_semantics', required
      - `reason` 'no_pdf_supplied' | 'not_implemented' | 'outside_scope' | 'reduced_profile', required
    - `scope` 'xml' | 'hybrid_pdf', required
  - `declared` Declared, required: What the document itself declares, copied from its source identifiers. A declaration, not a verdict: validation decides whether it is true.
    - `business_process_id` string: UBL ProfileID, or CII BusinessProcessSpecifiedDocumentContextParameter/ID.
    - `family` 'peppol-bis-billing-3' | 'xrechnung' | 'zugferd-facturx': An invoice family. `zugferd-facturx` is one family: ZUGFeRD and Factur-X are corresponding release names of the same standard.
    - `pdf` object: For a PDF, the invoice declarations in its XMP metadata.
      - `conformance_level` string
      - `document_file_name` string
      - `document_type` string
      - `pdfa_conformance` string
      - `pdfa_part` string
      - `version` string
    - `root` string: The root element's local name.
    - `specification_id` string: UBL CustomizationID, or CII GuidelineSpecifiedDocumentContextParameter/ID.
    - `syntax` 'ubl' | 'cii' | 'other', required: The syntax the root element names.
    - `type_code` string: The invoice type code, which decides invoice or credit note in CII.
    - `variant` 'core' | 'extension' | 'cvd' | 'minimum' | 'basic-wl' | 'basic' | 'en16931' | 'extended' | 'xrechnung': A variant or profile within a family. XRechnung: `core`, `extension`, `cvd`. ZUGFeRD / Factur-X: `minimum`, `basic-wl`, `basic`, `en16931`, `extended`, `xrechnung` (the ZUGFeRD XRECHNUNG reference profile). Peppol BIS Billing 3 has none. Recognition is not support: the ruleset list is the only statement of what is validated.
  - `engines` Engine[], required
    - `image_digest` string, required
    - `name` string, required
    - `role` 'xml' | 'pdf', required
  - `findings` Finding[], required
    - `hint_code` string
    - `layer` 'input' | 'pdfa' | 'hybrid_binding' | 'xml' | 'xsd' | 'en16931' | 'peppol' | 'xrechnung' | 'zugferd_profile', required: A validation stage. A result reports the stages its pipeline runs, in the pipeline's order; no pipeline runs them all.
    - `location` Location, required: Where a finding applies, in the source it came from: an XPath or a line and column in the invoice XML, or a PDF object or structure path. A PDF finding never carries an XML location.
      - `kind` 'none' | 'xpath' | 'line-column' | 'pdf', required
      - `value` string
    - `message` string, required: A fixed, safe message for the rule or layer. Official rule texts are not repeated, because they can carry invoice values.
    - `rule_flag` 'fatal' | 'warning' | 'information', required: `fatal` fails the document; `warning` and `information` never do.
    - `rule_id` string, required: The official rule identifier, unchanged. Ironfang's own checks use the `FW-` namespace (`rule_source: ironfang`).
    - `rule_source` 'official' | 'ironfang', required
    - `severity` 'fatal' | 'warning' | 'information', required: `fatal` fails the document; `warning` and `information` never do.
    - `source_url` string, uri
  - `findings_summary` FindingsSummary, required
    - `cap` 1000, required
    - `returned` integer, required
    - `total` integer, required
    - `truncated` boolean, required
  - `groups` CheckGroup[], required
    - `group` 'pdfa' | 'attachment_metadata' | 'invoice_xml', required
    - `layers` Layer[], required
    - `status` 'passed' | 'failed' | 'skipped', required
  - `input` ValidationInput, required
    - `bytes` integer, required
    - `document_type` 'invoice' | 'credit_note' | 'unknown', required
    - `embedded_xml` object: For a PDF, the invoice XML exactly as extracted - original bytes, never re-serialised.
      - `bytes` integer, required
      - `file_name` string: The attachment's name as declared in the PDF
      - `sha256` string, required
    - `media_type` 'application/xml' | 'application/pdf', required
    - `sha256` string, required
  - `layers` LayerResult[], required
    - `fatal` integer, required
    - `information` integer, required
    - `layer` 'input' | 'pdfa' | 'hybrid_binding' | 'xml' | 'xsd' | 'en16931' | 'peppol' | 'xrechnung' | 'zugferd_profile', required: A validation stage. A result reports the stages its pipeline runs, in the pipeline's order; no pipeline runs them all.
    - `reason` 'skipped_after_failure' | 'engine_error' | 'deadline_exceeded'
    - `status` 'passed' | 'failed' | 'skipped' | 'error', required
    - `warning` integer, required
  - `next_actions` string[], required: What this input supports next, from the capability registry: open the readable view, or extract the embedded XML. Never guessed by a client.
  - `operation_id` string
  - `outcome` 'valid' | 'invalid', required
  - `ruleset` RulesetSelection, required
    - `family` 'peppol-bis-billing-3' | 'xrechnung' | 'zugferd-facturx', required: An invoice family. `zugferd-facturx` is one family: ZUGFeRD and Factur-X are corresponding release names of the same standard.
    - `id` string, required
    - `official_release` string, required: The specification release
    - `requested` string, required
    - `scope` 'xml' | 'hybrid_pdf', required
    - `selection_method` 'exact' | 'latest' | 'detected', required
    - `state` 'scheduled' | 'sendable' | 'historical', required
    - `syntax` 'ubl-2.1' | 'cii-d16b' | 'cii-d22b', required: The syntax and schema edition a pipeline validates against, taken from the pinned release, never inferred from a namespace.
    - `technical_release` string: The technical bundle
    - `variant` 'core' | 'extension' | 'cvd' | 'minimum' | 'basic-wl' | 'basic' | 'en16931' | 'extended' | 'xrechnung': A variant or profile within a family. XRechnung: `core`, `extension`, `cvd`. ZUGFeRD / Factur-X: `minimum`, `basic-wl`, `basic`, `en16931`, `extended`, `xrechnung` (the ZUGFeRD XRECHNUNG reference profile). Peppol BIS Billing 3 has none. Recognition is not support: the ruleset list is the only statement of what is validated.
  - `schema` 'ironfang/finance/einvoice/validation-result/v2', required
  - `status` 'completed', required
  - `timing` Timing, required
    - `completed_at` string, date-time, required
    - `duration_ms` integer, required
    - `started_at` string, date-time, required
  - `usage` Usage
    - `charged` boolean, required
    - `credits` integer, required
    - `period_ends_at` string, date-time: Only on results recorded before usage billing, returned as they were recorded.
    - `remaining` integer: Only on results recorded before usage billing, returned as they were recorded.

## Other responses

- `400`: The request is malformed.
- `401`: The credential is missing or not accepted.
- `402`: The free allowance is used up and paid usage cannot continue until the billing account is put right: paused, no working payment method, a payment awaiting confirmation, or restricted. Nothing was accepted.
- `403`: The key lacks the scope this operation needs, or paid Ironfang Finance usage needs an approved business and the free allowance is used up. Nothing was accepted.
- `409`: The Idempotency-Key was used for a different request.
- `410`: A withdrawn ruleset, a replayed result that has expired or was deleted, or a closed billing account.
- `413`: The body is over the limit for its media type.
- `415`: The media type, encoding or content is not accepted.
- `422`: The selection cannot be made for this document, or the PDF cannot be processed. Uncharged.
- `429`: Rate limited, or the billing account can admit no more of this work this month.
- `500`: Ironfang Finance failed while validating. Indeterminate and uncharged.
- `503`: The validator for the selected ruleset is not available (indeterminate and uncharged), or billing is briefly unavailable (nothing was accepted).
- `504`: Validation did not finish within the deadline. Indeterminate and uncharged.

---

[API](https://skmtc.dev/ironfang/apis/ironfang-finance-e-invoicing-api-2.md) · [All operations](https://skmtc.dev/ironfang/apis/ironfang-finance-e-invoicing-api-2/llms.txt) · [OpenAPI document](https://skmtc.dev/ironfang/apis/ironfang-finance-e-invoicing-api-2/revisions/8140667e556b?raw)
