openapi: 3.2.0 info: title: Ironfang Finance E-Invoicing V2 Deliveries API version: 2.0.0 summary: Validate XRechnung, ZUGFeRD / Factur-X and Peppol invoices, as XML or as a PDF with embedded XML. description: 'V2 validates an e-invoice against the exact, immutable ruleset of its family and reports precisely what was checked.' contact: name: Ironfang email: hello@ironfang.uk url: https://ironfang.com/finance termsOfService: https://ironfang.com/legal/terms license: name: Proprietary identifier: LicenseRef-Ironfang-Proprietary servers: - url: https://api.ironfang.com/finance description: Production. Paths below are relative to this product prefix. tags: - name: Deliveries description: Webhook and S3 deliveries of V1 and V2 events together. paths: /v2/einvoices/deliveries: get: tags: - Deliveries operationId: listEInvoiceDeliveriesV2 summary: List webhook and S3 deliveries, V1 and V2 events together description: 'A page of the organisation''s deliveries, newest first. A finished operation queues one event, and the event one delivery per enabled destination (webhook or S3). An operation made through V1 carries the `financewolf/einvoice/event/v1` event; one made through V2 the `ironfang/finance/einvoice/event/v2` event, whose `result_url` is under `/finance/v2`. Each delivery says which (`api_version`); branch on the event''s `schema`. Pass `next_cursor` as `cursor` for the next page. Deliveries and events are kept for 30 days. V1''s own delivery list shows deliveries of V1 events only.' security: - platformKey: [] x-ironfang-scopes: - finance:einvoices:read parameters: - name: cursor in: query required: false schema: type: string minLength: 1 maxLength: 256 - name: limit in: query required: false schema: type: integer minimum: 1 maximum: 100 default: 50 - name: destination in: query required: false schema: $ref: '#/components/schemas/OpaqueID' - name: operation_id in: query required: false schema: $ref: '#/components/schemas/OpaqueID' - name: status in: query required: false description: '`retrying` is a pending delivery with at least one attempt behind it; `pending` one with none.' schema: type: string enum: - pending - retrying - delivering - delivered - failed - name: api_version in: query required: false schema: $ref: '#/components/schemas/APIVersion' responses: '200': description: The page. Cache-Control is no-store. content: application/json: schema: $ref: '#/components/schemas/DeliveryList' examples: union: summary: A V2 validation's delivery beside a V1 one externalValue: https://api.ironfang.com/finance/v2/examples/v2-delivery-list.json '400': $ref: '#/components/responses/MalformedRequest' '401': $ref: '#/components/responses/Unauthenticated' '403': $ref: '#/components/responses/InsufficientScope' '429': $ref: '#/components/responses/RateLimited' '500': $ref: '#/components/responses/InternalError' /v2/einvoices/deliveries/{id}: get: tags: - Deliveries operationId: getEInvoiceDeliveryV2 summary: One delivery and every attempt made description: 'One delivery of a V1 or a V2 event, with its event as queued and the history of every attempt: when it started and finished, the HTTP status the destination answered and why it failed, if it did.' security: - platformKey: [] x-ironfang-scopes: - finance:einvoices:read parameters: - name: id in: path required: true schema: $ref: '#/components/schemas/OpaqueID' responses: '200': description: The delivery. Cache-Control is no-store. content: application/json: schema: $ref: '#/components/schemas/DeliveryDetail' examples: v2: summary: A delivered V2 event externalValue: https://api.ironfang.com/finance/v2/examples/v2-delivery.json '401': $ref: '#/components/responses/Unauthenticated' '403': $ref: '#/components/responses/InsufficientScope' '404': $ref: '#/components/responses/NotFound' '429': $ref: '#/components/responses/RateLimited' '500': $ref: '#/components/responses/InternalError' /v2/einvoices/deliveries/{id}/retry: post: tags: - Deliveries operationId: retryEInvoiceDeliveryV2 summary: Retry a failed delivery description: 'Grants a failed delivery a fresh ladder of up to eight attempts (32 in all), keeping every earlier attempt in its history. Only a failed delivery to an enabled destination, whose event has not expired, can be retried; anything else is `400 malformed_request`. The body is empty.' security: - platformKey: [] x-ironfang-scopes: - finance:einvoices:destinations:manage parameters: - name: id in: path required: true schema: $ref: '#/components/schemas/OpaqueID' responses: '200': description: The delivery, pending again. Cache-Control is no-store. content: application/json: schema: $ref: '#/components/schemas/DeliveryDetail' '400': $ref: '#/components/responses/MalformedRequest' '401': $ref: '#/components/responses/Unauthenticated' '403': $ref: '#/components/responses/InsufficientScope' '404': $ref: '#/components/responses/NotFound' '429': $ref: '#/components/responses/RateLimited' '500': $ref: '#/components/responses/InternalError' '503': $ref: '#/components/responses/DeliveryUnavailable' components: schemas: BillingRefusal: type: object description: 'On a billing refusal only: the product and meter the unified billing account refused, and when the free allowance renews (the start of the next UTC month) where that is the reason.' additionalProperties: false properties: product: type: string enum: - finance meter: type: string enum: - finance.validation - finance.generation reset_at: type: string format: date-time EventV2: type: object additionalProperties: false description: 'The event of an operation made through V2, as a webhook body, an S3 manifest.json and here. Signed and delivered exactly as V1''s event. ' required: - schema - id - type - created_at - state_version - data properties: schema: type: string const: ironfang/finance/einvoice/event/v2 id: $ref: '#/components/schemas/OpaqueID' type: type: string enum: - einvoice.validation.completed - einvoice.operation.failed created_at: type: string format: date-time state_version: type: integer const: 1 data: type: object additionalProperties: false required: - operation_id - operation - status - outcome - input_sha256 - usage_charged - artifacts properties: operation_id: $ref: '#/components/schemas/OpaqueID' operation: type: string const: validate status: type: string enum: - completed - error outcome: type: string enum: - valid - invalid - indeterminate failure_code: type: string maxLength: 64 ruleset_id: $ref: '#/components/schemas/RulesetID' family: description: The family the result was made under; for a failure, as requested, which may be detect. type: string enum: - detect - peppol-bis-billing-3 - xrechnung - zugferd-facturx variant: description: The variant the result was made under; for a failure, as requested, which may be detect. type: string enum: - detect - core - extension - cvd - minimum - basic-wl - basic - en16931 - extended - xrechnung scope: description: What was checked, xml or hybrid_pdf; for a failure, as requested. type: string enum: - xml - hybrid_pdf input_sha256: $ref: '#/components/schemas/SHA256' usage_charged: type: boolean job_id: $ref: '#/components/schemas/OpaqueID' batch_id: $ref: '#/components/schemas/OpaqueID' result_url: type: string pattern: ^/finance/v2/einvoices/results/[0-9a-f-]{36}$ artifacts: type: array maxItems: 1 items: $ref: '#/components/schemas/DeliveryArtifact' Family: type: string description: 'An invoice family. `zugferd-facturx` is one family: ZUGFeRD and Factur-X are corresponding release names of the same standard.' enum: - peppol-bis-billing-3 - xrechnung - zugferd-facturx RequestID: type: string pattern: ^[A-Za-z0-9_-]{8,64}$ Declared: type: object description: 'What the document itself declares, copied from its source identifiers. A declaration, not a verdict: validation decides whether it is true.' additionalProperties: false required: - syntax properties: syntax: type: string description: The syntax the root element names. enum: - ubl - cii - other root: type: string description: The root element's local name. maxLength: 64 specification_id: type: string description: UBL CustomizationID, or CII GuidelineSpecifiedDocumentContextParameter/ID. maxLength: 256 business_process_id: type: string description: UBL ProfileID, or CII BusinessProcessSpecifiedDocumentContextParameter/ID. maxLength: 256 type_code: type: string description: The invoice type code, which decides invoice or credit note in CII. maxLength: 8 family: $ref: '#/components/schemas/Family' variant: $ref: '#/components/schemas/Variant' pdf: type: object description: For a PDF, the invoice declarations in its XMP metadata. additionalProperties: false properties: conformance_level: type: string maxLength: 32 document_file_name: type: string maxLength: 128 document_type: type: string maxLength: 32 version: type: string maxLength: 16 pdfa_part: type: string maxLength: 4 pdfa_conformance: type: string maxLength: 4 DeliveryDetail: type: object additionalProperties: false required: - schema - delivery - history properties: schema: type: string const: ironfang/finance/einvoice/delivery/v2 delivery: $ref: '#/components/schemas/Delivery' history: type: array maxItems: 32 items: $ref: '#/components/schemas/DeliveryAttempt' DeliveryList: type: object additionalProperties: false required: - schema - deliveries properties: schema: type: string const: ironfang/finance/einvoice/delivery-list/v2 deliveries: type: array maxItems: 100 items: $ref: '#/components/schemas/Delivery' next_cursor: type: string minLength: 1 maxLength: 256 DeliveryArtifact: type: object additionalProperties: false required: - name - content_type - sha256 - bytes properties: name: type: string enum: - result.json - document.xml content_type: type: string enum: - application/json - application/xml sha256: $ref: '#/components/schemas/SHA256' bytes: type: integer minimum: 1 Variant: type: string description: '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.' enum: - core - extension - cvd - minimum - basic-wl - basic - en16931 - extended - xrechnung Delivery: type: object additionalProperties: false required: - id - event_id - operation_id - destination_id - api_version - status - attempts - max_attempts - created_at - expires_at - next_attempt_at - event properties: id: $ref: '#/components/schemas/OpaqueID' event_id: $ref: '#/components/schemas/OpaqueID' operation_id: $ref: '#/components/schemas/OpaqueID' destination_id: $ref: '#/components/schemas/OpaqueID' api_version: $ref: '#/components/schemas/APIVersion' status: type: string enum: - pending - delivering - delivered - failed attempts: type: integer minimum: 0 maximum: 32 max_attempts: type: integer minimum: 1 maximum: 32 failure_code: type: string enum: - destination_unavailable - credentials_unavailable - network_error - http_rejected - storage_rejected - object_conflict - result_unavailable - internal_error - lease_expired - event_expired http_status: type: integer minimum: 1 maximum: 599 created_at: type: string format: date-time expires_at: type: string format: date-time finished_at: type: string format: date-time next_attempt_at: type: string format: date-time description: Scheduled time for pending work; ignore for terminal deliveries. event: description: The event as queued. Its schema matches api_version. oneOf: - $ref: '#/components/schemas/EventV1' - $ref: '#/components/schemas/EventV2' OpaqueID: type: string pattern: ^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$ EventV1: type: object description: 'The event of an operation made through V1 (financewolf/einvoice/event/v1), described in full by the V1 contract at https://api.ironfang.com/finance/openapi.yaml (DeliveryEvent). ' required: - schema - id - type - created_at - state_version - data properties: schema: type: string const: financewolf/einvoice/event/v1 id: $ref: '#/components/schemas/OpaqueID' type: type: string enum: - einvoice.validation.completed - einvoice.generation.completed - einvoice.operation.failed created_at: type: string format: date-time state_version: type: integer const: 1 data: type: object RulesetID: type: string pattern: ^fwrs_[a-z0-9_]{1,80}$ Problem: type: object description: 'An RFC 9457 problem. `code` is the stable identifier and fixes `status` and `type`. `detail` is safe to show: it never carries parser output, invoice content or stack traces.' additionalProperties: false required: - type - title - status - detail - code - request_id properties: type: type: string format: uri pattern: ^https://ironfang\.com/problems/[a-z_]+$ title: type: string minLength: 1 maxLength: 128 status: type: integer minimum: 400 maximum: 599 detail: type: string minLength: 1 maxLength: 512 instance: type: string format: uri-reference maxLength: 256 code: type: string enum: - malformed_request - unauthenticated - insufficient_scope - not_found - idempotency_conflict - ruleset_disabled - result_gone - payload_too_large - unsupported_content_type - unknown_ruleset - selection_conflict - family_undetected - family_mismatch - unsupported_variant - unsupported_document_type - scope_unavailable - no_embedded_invoice - ambiguous_embedded_invoice - encrypted_pdf - processing_limit - rate_limited - quota_exhausted - internal_error - validator_unavailable - validation_timeout - delivery_unavailable - jobs_unavailable - report_incomplete - report_unavailable - free_allowance_exhausted - account_budget_exhausted - product_budget_exhausted - exemption_limit_reached - paid_usage_paused - payment_required - payment_action_required - account_restricted - account_closed - product_not_eligible - billing_temporarily_unavailable request_id: $ref: '#/components/schemas/RequestID' errors: type: array description: Field-level errors, on `malformed_request` only. minItems: 1 maxItems: 50 items: $ref: '#/components/schemas/FieldError' detected: $ref: '#/components/schemas/Declared' description: 'On `family_mismatch`, `family_undetected` and `unsupported_variant` only: what the document declares, so a client can point to the right tool. Identifiers only, never invoice content.' operation_id: $ref: '#/components/schemas/OpaqueID' description: On an indeterminate validation only, the operation that was opened. outcome: type: string const: indeterminate usage_charged: type: boolean const: false retry_after_seconds: type: integer minimum: 1 maximum: 3600 billing: $ref: '#/components/schemas/BillingRefusal' allOf: - if: properties: code: const: malformed_request then: properties: status: const: 400 type: const: https://ironfang.com/problems/malformed_request - if: properties: code: const: unauthenticated then: properties: status: const: 401 type: const: https://ironfang.com/problems/unauthenticated - if: properties: code: const: insufficient_scope then: properties: status: const: 403 type: const: https://ironfang.com/problems/insufficient_scope - if: properties: code: const: not_found then: properties: status: const: 404 type: const: https://ironfang.com/problems/not_found - if: properties: code: const: idempotency_conflict then: properties: status: const: 409 type: const: https://ironfang.com/problems/idempotency_conflict - if: properties: code: const: ruleset_disabled then: properties: status: const: 410 type: const: https://ironfang.com/problems/ruleset_disabled - if: properties: code: const: result_gone then: properties: status: const: 410 type: const: https://ironfang.com/problems/result_gone - if: properties: code: const: payload_too_large then: properties: status: const: 413 type: const: https://ironfang.com/problems/payload_too_large - if: properties: code: const: unsupported_content_type then: properties: status: const: 415 type: const: https://ironfang.com/problems/unsupported_content_type - if: properties: code: const: unknown_ruleset then: properties: status: const: 422 type: const: https://ironfang.com/problems/unknown_ruleset - if: properties: code: const: selection_conflict then: properties: status: const: 422 type: const: https://ironfang.com/problems/selection_conflict - if: properties: code: const: family_undetected then: properties: status: const: 422 type: const: https://ironfang.com/problems/family_undetected - if: properties: code: const: family_mismatch then: properties: status: const: 422 type: const: https://ironfang.com/problems/family_mismatch - if: properties: code: const: unsupported_variant then: properties: status: const: 422 type: const: https://ironfang.com/problems/unsupported_variant - if: properties: code: const: unsupported_document_type then: properties: status: const: 422 type: const: https://ironfang.com/problems/unsupported_document_type - if: properties: code: const: scope_unavailable then: properties: status: const: 422 type: const: https://ironfang.com/problems/scope_unavailable - if: properties: code: const: no_embedded_invoice then: properties: status: const: 422 type: const: https://ironfang.com/problems/no_embedded_invoice - if: properties: code: const: ambiguous_embedded_invoice then: properties: status: const: 422 type: const: https://ironfang.com/problems/ambiguous_embedded_invoice - if: properties: code: const: encrypted_pdf then: properties: status: const: 422 type: const: https://ironfang.com/problems/encrypted_pdf - if: properties: code: const: processing_limit then: properties: status: const: 422 type: const: https://ironfang.com/problems/processing_limit - if: properties: code: const: rate_limited then: properties: status: const: 429 type: const: https://ironfang.com/problems/rate_limited - if: properties: code: const: quota_exhausted then: properties: status: const: 429 type: const: https://ironfang.com/problems/quota_exhausted - if: properties: code: const: internal_error then: properties: status: const: 500 type: const: https://ironfang.com/problems/internal_error - if: properties: code: const: validator_unavailable then: properties: status: const: 503 type: const: https://ironfang.com/problems/validator_unavailable - if: properties: code: const: delivery_unavailable then: properties: status: const: 503 type: const: https://ironfang.com/problems/delivery_unavailable - if: properties: code: const: jobs_unavailable then: properties: status: const: 503 type: const: https://ironfang.com/problems/jobs_unavailable - if: properties: code: const: report_incomplete then: properties: status: const: 422 type: const: https://ironfang.com/problems/report_incomplete - if: properties: code: const: report_unavailable then: properties: status: const: 503 type: const: https://ironfang.com/problems/report_unavailable - if: properties: code: const: validation_timeout then: properties: status: const: 504 type: const: https://ironfang.com/problems/validation_timeout - if: properties: code: const: free_allowance_exhausted then: properties: status: const: 429 type: const: https://ironfang.com/problems/free_allowance_exhausted - if: properties: code: const: account_budget_exhausted then: properties: status: const: 429 type: const: https://ironfang.com/problems/account_budget_exhausted - if: properties: code: const: product_budget_exhausted then: properties: status: const: 429 type: const: https://ironfang.com/problems/product_budget_exhausted - if: properties: code: const: exemption_limit_reached then: properties: status: const: 429 type: const: https://ironfang.com/problems/exemption_limit_reached - if: properties: code: const: paid_usage_paused then: properties: status: const: 402 type: const: https://ironfang.com/problems/paid_usage_paused - if: properties: code: const: payment_required then: properties: status: const: 402 type: const: https://ironfang.com/problems/payment_required - if: properties: code: const: payment_action_required then: properties: status: const: 402 type: const: https://ironfang.com/problems/payment_action_required - if: properties: code: const: account_restricted then: properties: status: const: 402 type: const: https://ironfang.com/problems/account_restricted - if: properties: code: const: account_closed then: properties: status: const: 410 type: const: https://ironfang.com/problems/account_closed - if: properties: code: const: product_not_eligible then: properties: status: const: 403 type: const: https://ironfang.com/problems/product_not_eligible - if: properties: code: const: billing_temporarily_unavailable then: properties: status: const: 503 type: const: https://ironfang.com/problems/billing_temporarily_unavailable APIVersion: type: string description: The API a result was made through. enum: - v1 - v2 FieldError: type: object additionalProperties: false required: - field - code - message properties: field: type: string minLength: 1 maxLength: 64 code: type: string enum: - unknown_field - repeated - invalid_value - missing message: type: string minLength: 1 maxLength: 256 SHA256: type: string pattern: ^[0-9a-f]{64}$ DeliveryAttempt: type: object additionalProperties: false required: - attempt - started_at properties: attempt: type: integer minimum: 1 maximum: 32 started_at: type: string format: date-time finished_at: type: string format: date-time http_status: type: integer minimum: 1 maximum: 599 failure_code: type: string maxLength: 64 responses: InternalError: description: Ironfang Finance failed. Not a verdict on anything. content: application/problem+json: schema: allOf: - $ref: '#/components/schemas/Problem' - properties: code: const: internal_error DeliveryUnavailable: description: Destination delivery is not configured on this deployment. headers: Retry-After: $ref: '#/components/headers/RetryAfter' content: application/problem+json: schema: allOf: - $ref: '#/components/schemas/Problem' - properties: code: const: delivery_unavailable NotFound: description: No such release or result is visible. content: application/problem+json: schema: allOf: - $ref: '#/components/schemas/Problem' - properties: code: const: not_found RateLimited: description: Too many requests. headers: Retry-After: $ref: '#/components/headers/RetryAfter' content: application/problem+json: schema: allOf: - $ref: '#/components/schemas/Problem' - properties: code: const: rate_limited MalformedRequest: description: The request is malformed. content: application/problem+json: schema: allOf: - $ref: '#/components/schemas/Problem' - properties: code: const: malformed_request InsufficientScope: description: The key lacks the scope this operation needs. content: application/problem+json: schema: allOf: - $ref: '#/components/schemas/Problem' - properties: code: const: insufficient_scope Unauthenticated: description: The credential is missing or not accepted. content: application/problem+json: schema: allOf: - $ref: '#/components/schemas/Problem' - properties: code: const: unauthenticated headers: RetryAfter: description: Seconds to wait before retrying. Mirrors `retry_after_seconds`. schema: type: integer minimum: 1 securitySchemes: platformKey: type: http scheme: bearer description: An Ironfang platform API key minted in the portal, carrying the scopes an operation lists. externalDocs: description: Ironfang Finance documentation url: https://ironfang.com/docs/finance