Skip to content

Types & Errors

SDFMeta

SDFMeta is the Python dataclass representation of meta.json. It is the input to SDFProducer.build() and the output of parse_sdf().

from sdf.types import SDFMeta
# or directly:
from sdf import SDFMeta

Definition

from dataclasses import dataclass, field
from uuid import uuid4
from datetime import datetime, timezone
@dataclass
class SDFMeta:
# Required fields
issuer: str
document_type: str
# Optional business fields
issuer_id: str = ""
recipient: str = ""
schema_id: str = ""
locale: str = ""
nomination_ref: str = ""
# Auto-generated — do not set manually
sdf_version: str = "0.1"
document_id: str = field(default_factory=lambda: str(uuid4()))
issued_at: str = field(
default_factory=lambda: datetime.now(timezone.utc).isoformat()
)

Fields

FieldTypeRequiredDescription
issuerstrYesName of the document issuer
document_typestrYesType identifier: "invoice", "nomination", "purchase-order", etc.
issuer_idstrNoIssuer registry ID (e.g. VAT number, company registration)
recipientstrNoName of the document recipient
schema_idstrNoSchema identifier in {type}/v{version} format, e.g. "invoice/v0.2"
localestrNoBCP 47 locale tag, e.g. "de-DE", "tr-TR", "en-US"
nomination_refstrNoCross-reference for nomination matching workflows
sdf_versionstrAutoSDF specification version. Always "0.1". Do not set.
document_idstrAutoUUID v4 generated at instantiation time. Do not set from business data.
issued_atstrAutoISO 8601 UTC timestamp. Generated at instantiation time.

Important: document_id

document_id is always a fresh UUID v4 generated when the SDFMeta object is created. It is not derived from any business identifier. Business identifiers such as invoice numbers, nomination references, or purchase order numbers belong in data.json.

# Correct
meta = SDFMeta(issuer="Acme", document_type="invoice")
# meta.document_id → "3f8a1c2d-e4f5-4a6b-b7c8-9d0e1f2a3b4c" (auto)
data = {
"invoice_number": "INV-2026-001", # business ID goes here
...
}
# Wrong — do not copy a business ID into document_id
meta = SDFMeta(
issuer="Acme",
document_type="invoice",
document_id="INV-2026-001", # violates the spec
)

SDFResult

SDFResult is the return type of parse_sdf(). It contains all files extracted from the .sdf archive.

from sdf.types import SDFResult

Definition

@dataclass
class SDFResult:
meta: SDFMeta # Parsed meta.json
data: dict # Parsed data.json
schema: dict # Parsed schema.json
visual: bytes # Raw PDF bytes from visual.pdf
signature: bytes | None # Raw signature.sig bytes, or None if absent

Fields

FieldTypeDescription
metaSDFMetaThe document metadata
datadictThe document business data payload
schemadictThe JSON Schema used to validate data
visualbytesThe visual representation as raw PDF bytes
signaturebytes | NoneThe digital signature bytes, or None for unsigned documents

ValidationResult

Returned by validate_schema().

@dataclass
class ValidationResult:
valid: bool
errors: list[ValidationError]
@dataclass
class ValidationError:
path: str # JSON pointer to the failing location in data
message: str # Human-readable description
schema_path: str # JSON pointer into the schema that failed

SDFError

SDFError is the single exception class raised by all SDK operations.

from sdf.errors import SDFError
class SDFError(Exception):
def __init__(self, code: str, message: str):
self.code = code
self.message = message
super().__init__(f"[{code}] {message}")

Error codes

All error codes are defined in the SDF specification (Section 12). Do not invent new codes.

CodeRaised when
SDF_ERROR_NOT_ZIPThe file is not a valid ZIP archive
SDF_ERROR_INVALID_METAmeta.json fails schema validation or has invalid field values
SDF_ERROR_MISSING_FILEA required file (visual.pdf, data.json, schema.json, meta.json) is absent from the archive
SDF_ERROR_SCHEMA_MISMATCHdata.json does not satisfy schema.json
SDF_ERROR_INVALID_SCHEMAschema.json is not a valid JSON Schema Draft 2020-12 document
SDF_ERROR_UNSUPPORTED_VERSIONmeta.json → sdf_version is not a supported version
SDF_ERROR_INVALID_SIGNATURESignature verification failed, or a signed document has a missing or malformed signature.sig
SDF_ERROR_INVALID_ARCHIVEPath traversal detected in a ZIP entry, or the archive is otherwise corrupt
SDF_ERROR_ARCHIVE_TOO_LARGEA single file exceeds 50 MB or total uncompressed size exceeds 200 MB

Handling errors

error-handling.py
from sdf import parse_sdf, validate_schema
from sdf.errors import SDFError
def process_sdf(path: str) -> None:
try:
with open(path, "rb") as f:
result = parse_sdf(f.read())
validation = validate_schema(result.data, result.schema)
if not validation.valid:
raise SDFError(
"SDF_ERROR_SCHEMA_MISMATCH",
"; ".join(f"{e.path}: {e.message}" for e in validation.errors),
)
print(f"OK: {result.meta.document_id} ({result.meta.document_type})")
except SDFError as e:
print(f"SDF error [{e.code}]: {e.message}")
except FileNotFoundError:
print(f"File not found: {path}")

Error code lookup

error-lookup.py
from sdf.errors import SDFError, SDF_ERROR_CODES
# All defined error codes as a set
print(SDF_ERROR_CODES)
# {
# 'SDF_ERROR_NOT_ZIP',
# 'SDF_ERROR_INVALID_META',
# 'SDF_ERROR_MISSING_FILE',
# 'SDF_ERROR_SCHEMA_MISMATCH',
# 'SDF_ERROR_INVALID_SCHEMA',
# 'SDF_ERROR_UNSUPPORTED_VERSION',
# 'SDF_ERROR_INVALID_SIGNATURE',
# 'SDF_ERROR_INVALID_ARCHIVE',
# 'SDF_ERROR_ARCHIVE_TOO_LARGE',
# }
# Check if a code is known
def is_known_error(code: str) -> bool:
return code in SDF_ERROR_CODES