xingen

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 10, 2026 License: MIT Imports: 18 Imported by: 0

README

xingen-go-sdk

Go client SDK for the Xingen e-invoice validation API — submit UBL, CII, ZUGFeRD, and SAP IDoc/OData invoices for validation against EN16931, XRechnung, and Peppol.

Requires Go 1.23+ (for iter.Seq2-based pagination — see List and retrieve invoices). Zero third-party dependencies — built entirely on the standard library (net/http, mime/multipart, encoding/json).

Status: v1, covering invoice submission/validation and API key management. Contacts and dashboard/user endpoints are not exposed (they're Firebase-auth-only on the backend).

Install

go get github.com/nko-technologies/xingen-go-sdk
import "github.com/nko-technologies/xingen-go-sdk"

Authentication

Every request needs an API key (xgn_live_... for production, xgn_test_... for sandbox — sandbox requests never count toward quota). Create one from the Xingen dashboard or via client.APIKeys.

client, err := xingen.New(os.Getenv("XINGEN_API_KEY"))
if err != nil {
    log.Fatal(err)
}

Client holds one connection-pooled *http.Client — construct it once and reuse it, don't rebuild it per request. xingen.WithBaseURL(...) overrides the default https://app.xingen.de/api, useful for self-hosted or local (./gradlew bootRun, port 10001) testing.

Every method takes a context.Context as its first argument — use it for request cancellation and deadlines the way you would with any other Go client. xingen.WithRequestTimeout sets a per-request fallback timeout (30s by default) that applies on top of whatever your context does.

Validate a file

Every validate/submit endpoint is asynchronous — the backend queues the invoice and returns immediately. Use a *AndWait helper to submit and poll for the result in one call:

record, err := client.Invoices.ValidateFileAndWait(ctx, "invoice.xml", xingen.ProfileXRechnung, xingen.DefaultPollOptions())
if err != nil {
    log.Fatal(err)
}

if record.Status == xingen.StatusValidated && record.ValidationResult.Valid {
    fmt.Println("Valid!")
} else {
    for _, e := range record.ValidationResult.Errors {
        fmt.Printf("%s: %s (%s)\n", *e.Severity, *e.Message, *e.Field)
    }
}

PollOptions controls the backoff (InitialInterval, MaxInterval, BackoffMultiplier) and the overall Timeout. Cancel the poll early by cancelling the context.Context you pass in — there's no separate cancellation-check option like some other language SDKs use, since context.Context is already Go's standard mechanism for this. A failed validation is not an error — it's a completed API call that found the invoice invalid, so *AndWait returns normally with record.ValidationResult.Valid == false. Only a transport failure, context cancellation, or a PollOptions.Timeout elapsing returns an error (*xingen.PollCancelledError / *xingen.PollTimeoutError, the latter carrying PartialResult with the last known state).

opts := xingen.PollOptions{
    InitialInterval:   300 * time.Millisecond,
    MaxInterval:       3 * time.Second,
    BackoffMultiplier: 1.5,
    Timeout:           30 * time.Second,
}

If you'd rather manage polling yourself, use the low-level pair:

submitted, err := client.Invoices.ValidateFile(ctx, "invoice.xml", xingen.ProfileEN16931)
// ... later ...
record, err := client.Invoices.Get(ctx, submitted.ID)

ValidateIdoc / ValidateIdocAndWait work the same way for SAP IDoc XML files. Every ValidateX method also has a ValidateXBytes counterpart if you already hold the file bytes in memory instead of a path.

Submit a structured invoice (JSON)

submission := xingen.InvoiceSubmission{
    InvoiceNumber:     "INV-2024-0042",
    IssueDate:         xingen.NewDate(time.Date(2024, 3, 15, 0, 0, 0, 0, time.UTC)),
    Currency:          "EUR",
    BuyerReference:    "991-12345-06",
    ValidationProfile: xingen.ProfileXRechnung,
    Supplier:          xingen.InvoiceSubmissionPartyInput{Name: "Acme GmbH", VatID: "DE123456789"},
    Buyer:             xingen.InvoiceSubmissionPartyInput{Name: "Buyer Co", LeitwegID: "991-12345-06"},
    Lines: []xingen.InvoiceSubmissionLineInput{
        {Description: "Software License Q1", Quantity: "5", Unit: "C62", Price: "199.00", TaxRate: "19"},
    },
}

record, err := client.Invoices.SubmitAndWait(ctx, submission, xingen.DefaultPollOptions())

Quantity/Price/TaxRate are xingen.Number — a type alias for encoding/json.Number (itself just a string), so you can pass any string-shaped numeric literal without losing precision. See Design notes for why.

SAP S/4HANA OData supplier-invoice payloads are supported as a thin passthrough — pass raw JSON bytes or a map[string]any rather than a fully typed model:

client.Invoices.SubmitOData(ctx, rawODataJSON, xingen.ProfileEN16931)

List and retrieve invoices

page, err := client.Invoices.List(ctx, 0, 20, "createdAt,desc")

// or, to walk every invoice without managing page indices yourself — ListAll returns an
// iter.Seq2[InvoiceRecord, error], so a plain range loop lazily fetches pages as needed:
for record, err := range client.Invoices.ListAll(ctx, 50) {
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(record.ID, "->", record.Status)
}

one, err := client.Invoices.Get(ctx, "inv_01HXYZ")

Download results

pdf, err := client.Invoices.DownloadPDF(ctx, id)       // ZUGFeRD PDF with embedded XML
idocXML, err := client.Invoices.DownloadIdocXML(ctx, id) // SAP IDoc XML

API keys

created, err := client.APIKeys.Create(ctx, xingen.CreateApiKeyRequest{Name: "Production CI"})
fmt.Println("Store this now, it's shown only once:", created.RawKey)

keys, err := client.APIKeys.List(ctx)
err = client.APIKeys.Revoke(ctx, created.ID)

Error handling

Every non-2xx response maps to a typed error, all of which embed *xingen.APIError (StatusCode / RawBody / ErrorResponseBody). Use errors.As to match the specific type you care about:

Error type Status Notes
*xingen.AuthenticationError 401 Missing or invalid API key
*xingen.PermissionError 403 Resource exists but isn't owned by the caller
*xingen.NotFoundError 404
*xingen.ValidationRequestError 400 FieldErrors has details for request-body validation failures
*xingen.QuotaExceededError 429 Monthly request quota exhausted
*xingen.APIError other 4xx/5xx Fallback
_, err := client.Invoices.Submit(ctx, submission)

var validationErr *xingen.ValidationRequestError
var quotaErr *xingen.QuotaExceededError
switch {
case errors.As(err, &validationErr):
    for field, message := range validationErr.FieldErrors {
        fmt.Println(field, ":", message)
    }
case errors.As(err, &quotaErr):
    fmt.Println("Quota exceeded — upgrade or wait for the next billing period")
case err != nil:
    fmt.Println("Request failed:", err)
}

A network-level failure (no HTTP response at all) surfaces as *xingen.TransportError, which wraps the underlying error via Unwrap()errors.Is(err, context.DeadlineExceeded) works as expected if your context's deadline is what caused it.

Design notes

  • No custom JSON parser needed for monetary precision, unlike this SDK's Python/TypeScript/PHP siblings. Go's encoding/json decodes a json.Number-typed struct field straight from the source-text numeric literal — no float64 intermediate — for free; xingen.Number is just an alias for it. Confirmed empirically against a 30-digit literal in wire_test.go.
  • context.Context instead of a cancellation callback. Every method takes ctx first, and the *AndWait polling helpers respect it directly instead of a separate cancellationCheck option (which the Java/Python/TypeScript/C#/PHP ports use, since none of those languages have a first-class cancellation primitive baked into every stdlib I/O call the way Go does).
  • iter.Seq2 instead of a hand-rolled iterator for ListAll (Go 1.23+'s range-over-func), the same idiom-over-literal-port adaptation the C# SDK made with IAsyncEnumerable.
  • No automatic retries. Retrying a Submit after a client-side timeout is unsafe without idempotency keys, which the API doesn't support yet — a retried submit could create a duplicate invoice. Handle retries at the call site if you need them.

Contributing

go build ./... && go vet ./... && go test ./... -race

Tests run against a real (loopback) net/http/httptest.Server, not a mocking framework — no network calls leave the machine.

License

MIT — see LICENSE.

Documentation

Overview

Package xingen is a client SDK for the Xingen (https://xingen.de) e-invoice validation API — submit UBL, CII, ZUGFeRD, and SAP IDoc/OData invoices for validation against EN16931, XRechnung, and Peppol.

Construct a Client with New, then use its Invoices and APIKeys fields to reach the resource-specific services. A single Client holds one connection-pooled *http.Client and should be reused across calls rather than rebuilt per request.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type APIError

type APIError struct {
	Message string
	// StatusCode is the HTTP status code of the response.
	StatusCode int
	// RawBody is the raw response body, always retained even if it couldn't be parsed as ErrorResponse.
	RawBody string
	// ErrorResponseBody is nil if the response body didn't match the expected shape (e.g. an
	// unexpected 5xx from a proxy in front of the backend).
	ErrorResponseBody *ErrorResponse
}

APIError is any HTTP response the SDK understands as an error (4xx/5xx). Every error type returned by this SDK for a non-2xx response embeds *APIError, so callers can always reach StatusCode/RawBody/ErrorResponse via the interface below regardless of the concrete error type.

func (*APIError) Error

func (e *APIError) Error() string

type APIKeysService

type APIKeysService struct {
	// contains filtered or unexported fields
}

APIKeysService creates, lists, and revokes API keys. Reachable via Client.APIKeys.

func (*APIKeysService) Create

Create makes a new API key. The RawKey on the result is shown only this once — persist it immediately.

func (*APIKeysService) List

func (s *APIKeysService) List(ctx context.Context) ([]ApiKey, error)

List returns every API key belonging to the caller.

func (*APIKeysService) Revoke

func (s *APIKeysService) Revoke(ctx context.Context, id string) error

Revoke permanently deactivates the API key with the given id.

type Address

type Address struct {
	StreetName           *string `json:"streetName"`
	AdditionalStreetName *string `json:"additionalStreetName"`
	AddressLine3         *string `json:"addressLine3"`
	City                 *string `json:"city"`
	PostalZone           *string `json:"postalZone"`
	CountrySubdivision   *string `json:"countrySubdivision"`
	CountryCode          *string `json:"countryCode"`
}

type AllowanceCharge

type AllowanceCharge struct {
	// Charge is true for a charge, false for an allowance.
	Charge          bool    `json:"charge"`
	Amount          *Number `json:"amount"`
	BaseAmount      *Number `json:"baseAmount"`
	Percentage      *Number `json:"percentage"`
	VatCategoryCode *string `json:"vatCategoryCode"`
	VatRate         *Number `json:"vatRate"`
	Reason          *string `json:"reason"`
	ReasonCode      *string `json:"reasonCode"`
}

AllowanceCharge is a document-level allowance/charge (BG-20/BG-21).

type ApiKey

type ApiKey struct {
	ID        string `json:"id"`
	Name      string `json:"name"`
	KeyPrefix string `json:"keyPrefix"`
	Sandbox   bool   `json:"sandbox"`
	Active    bool   `json:"active"`
	// QuotaLimit is nil for unlimited.
	QuotaLimit *int       `json:"quotaLimit"`
	QuotaUsed  int        `json:"quotaUsed"`
	LastUsedAt *time.Time `json:"lastUsedAt"`
	CreatedAt  time.Time  `json:"createdAt"`
	// RevokedAt is nil if the key is still active.
	RevokedAt *time.Time `json:"revokedAt"`
}

ApiKey is API key metadata as returned by List/Create — the raw key value is never included here.

type AuthenticationError

type AuthenticationError struct{ *APIError }

AuthenticationError is a 401 — missing or invalid API key. The backend returns no application-level body for this status.

type Client

type Client struct {
	// Invoices submits invoices for validation and retrieves results.
	Invoices *InvoicesService
	// APIKeys creates, lists, and revokes API keys.
	APIKeys *APIKeysService
	// contains filtered or unexported fields
}

Client is the entry point for the Xingen API.

func New

func New(apiKey string, opts ...Option) (*Client, error)

New constructs a Client. apiKey is required — an "xgn_live_"/"xgn_test_" prefixed API key.

type Contact

type Contact struct {
	Name      *string `json:"name"`
	Telephone *string `json:"telephone"`
	Email     *string `json:"email"`
}

type CreateApiKeyRequest

type CreateApiKeyRequest struct {
	Name string `json:"name"`
	// Sandbox: if true, requests using this key don't count toward quota. Defaults to false.
	Sandbox bool `json:"sandbox"`
	// QuotaLimit is an optional monthly quota. Nil means unlimited (Pro only) — free-tier keys
	// are capped server-side regardless.
	QuotaLimit *int `json:"quotaLimit,omitempty"`
}

CreateApiKeyRequest is the request body for APIKeysService.Create.

type CreatedApiKey

type CreatedApiKey struct {
	ID         string    `json:"id"`
	RawKey     string    `json:"rawKey"`
	Name       string    `json:"name"`
	Sandbox    bool      `json:"sandbox"`
	QuotaLimit *int      `json:"quotaLimit"`
	CreatedAt  time.Time `json:"createdAt"`
}

CreatedApiKey is the response from APIKeysService.Create. RawKey is shown only this once — the backend never returns it again, so callers must persist it immediately.

type Date

type Date string

Date is a date-only value (no time-of-day or timezone), formatted as "YYYY-MM-DD" on the wire.

It is a defined string type, not a struct — encoding/json marshals/unmarshals any string-kind type as a plain JSON string automatically, so no custom MarshalJSON/UnmarshalJSON is needed here (the same trick Number relies on). Use NewDate to build one from a time.Time and Time to parse one back.

func NewDate

func NewDate(t time.Time) Date

NewDate formats t's date components (ignoring time-of-day and location) as a Date.

func (Date) IsZero

func (d Date) IsZero() bool

IsZero reports whether d is the empty Date, i.e. the field was absent from the source document.

func (Date) String

func (d Date) String() string

func (Date) Time

func (d Date) Time() (time.Time, error)

Time parses d as a UTC time.Time at midnight. Returns an error if d is not a valid "YYYY-MM-DD" date.

type Delivery

type Delivery struct {
	PartyName        *string `json:"partyName"`
	LocationID       *string `json:"locationId"`
	LocationSchemeID *string `json:"locationSchemeId"`
	// Address is the deliver-to address (BG-15); nil iff absent from the source document.
	Address            *Address `json:"address"`
	ActualDeliveryDate *Date    `json:"actualDeliveryDate"`
}

type ErrorResponse

type ErrorResponse struct {
	Message     string            `json:"message"`
	Error       string            `json:"error"`
	Code        int               `json:"code"`
	Timestamp   time.Time         `json:"timestamp"`
	FieldErrors map[string]string `json:"fieldErrors"`
}

ErrorResponse mirrors the backend's standard error body shape. Present on 400/403/404/500 responses. Not present on 429 (see QuotaExceededError) or 401, which use a different or empty body.

type Invoice

type Invoice struct {
	InvoiceNumber            *string  `json:"invoiceNumber"`
	IssueDate                *Date    `json:"issueDate"`
	DueDate                  *Date    `json:"dueDate"`
	TaxPointDate             *Date    `json:"taxPointDate"`
	Currency                 *string  `json:"currency"`
	BuyerReference           *string  `json:"buyerReference"`
	SpecificationID          *string  `json:"specificationId"`
	ProfileID                *string  `json:"profileId"`
	TypeCode                 *string  `json:"typeCode"`
	OrderReference           *string  `json:"orderReference"`
	SalesOrderReference      *string  `json:"salesOrderReference"`
	ProjectReference         *string  `json:"projectReference"`
	ContractReference        *string  `json:"contractReference"`
	ReceivingAdviceReference *string  `json:"receivingAdviceReference"`
	DespatchAdviceReference  *string  `json:"despatchAdviceReference"`
	TenderOrLotReference     *string  `json:"tenderOrLotReference"`
	InvoicedObjectID         *string  `json:"invoicedObjectId"`
	InvoicedObjectSchemeID   *string  `json:"invoicedObjectSchemeId"`
	BuyerAccountingReference *string  `json:"buyerAccountingReference"`
	Notes                    []string `json:"notes"`
	PaymentTermsNote         *string  `json:"paymentTermsNote"`

	PrecedingInvoiceReferences []PrecedingInvoiceReference `json:"precedingInvoiceReferences"`
	SupportingDocuments        []SupportingDocument        `json:"supportingDocuments"`
	ProjectReferenceCount      int                         `json:"projectReferenceCount"`

	DeliveryPeriodStart *Date `json:"deliveryPeriodStart"`
	DeliveryPeriodEnd   *Date `json:"deliveryPeriodEnd"`

	// InvoicePeriod is nil iff no document-level invoicing period was present in the source document.
	InvoicePeriod *InvoicePeriod `json:"invoicePeriod"`
	// Delivery is nil iff no delivery element was present in the source document.
	Delivery *Delivery `json:"delivery"`

	Supplier *Party `json:"supplier"`
	Buyer    *Party `json:"buyer"`
	// Payee is nil unless the payee differs from the seller.
	Payee *Party `json:"payee"`
	// TaxRepresentative is nil unless a tax representative is present.
	TaxRepresentative *Party `json:"taxRepresentative"`

	Lines            []InvoiceLine     `json:"lines"`
	TaxBreakdowns    []TaxBreakdown    `json:"taxBreakdowns"`
	AllowanceCharges []AllowanceCharge `json:"allowanceCharges"`
	PaymentMeans     []PaymentMeans    `json:"paymentMeans"`

	TaxCurrencyCode               *string `json:"taxCurrencyCode"`
	TaxTotalWithSubtotalsCount    int     `json:"taxTotalWithSubtotalsCount"`
	TaxTotalWithoutSubtotalsCount int     `json:"taxTotalWithoutSubtotalsCount"`

	LineExtensionAmount           *Number `json:"lineExtensionAmount"`
	AllowanceTotalAmount          *Number `json:"allowanceTotalAmount"`
	ChargeTotalAmount             *Number `json:"chargeTotalAmount"`
	TaxExclusiveAmount            *Number `json:"taxExclusiveAmount"`
	TaxAmount                     *Number `json:"taxAmount"`
	TaxAmountInAccountingCurrency *Number `json:"taxAmountInAccountingCurrency"`
	TaxInclusiveAmount            *Number `json:"taxInclusiveAmount"`
	PrepaidAmount                 *Number `json:"prepaidAmount"`
	PayableRoundingAmount         *Number `json:"payableRoundingAmount"`
	PayableAmount                 *Number `json:"payableAmount"`
}

Invoice is the read-only invoice model, as returned inside an InvoiceRecord.

type InvoiceLine

type InvoiceLine struct {
	LineID                 *string `json:"lineId"`
	Note                   *string `json:"note"`
	ObjectID               *string `json:"objectId"`
	ObjectIDSchemeID       *string `json:"objectIdSchemeId"`
	OrderLineReference     *string `json:"orderLineReference"`
	AccountingReference    *string `json:"accountingReference"`
	ItemName               *string `json:"itemName"`
	Description            *string `json:"description"`
	SellerItemID           *string `json:"sellerItemId"`
	BuyerItemID            *string `json:"buyerItemId"`
	StandardItemID         *string `json:"standardItemId"`
	StandardItemIDSchemeID *string `json:"standardItemIdSchemeId"`
	OriginCountryCode      *string `json:"originCountryCode"`

	Classifications []ItemClassification `json:"classifications"`
	Attributes      []ItemAttribute      `json:"attributes"`

	Quantity              *Number `json:"quantity"`
	Unit                  *string `json:"unit"`
	Price                 *Number `json:"price"`
	GrossPrice            *Number `json:"grossPrice"`
	PriceDiscount         *Number `json:"priceDiscount"`
	PriceHasCharge        bool    `json:"priceHasCharge"`
	PriceBaseQuantity     *Number `json:"priceBaseQuantity"`
	PriceBaseQuantityUnit *string `json:"priceBaseQuantityUnit"`
	TaxCategoryCode       *string `json:"taxCategoryCode"`
	TaxRate               *Number `json:"taxRate"`
	LineNetAmount         *Number `json:"lineNetAmount"`

	// Period is nil iff no line-level invoicing period was present in the source document.
	Period                    *InvoicePeriod `json:"period"`
	DocumentReferenceCount    int            `json:"documentReferenceCount"`
	DocumentReferenceTypeCode *string        `json:"documentReferenceTypeCode"`

	AllowanceCharges []LineAllowanceCharge `json:"allowanceCharges"`
}

type InvoicePeriod

type InvoicePeriod struct {
	StartDate *Date `json:"startDate"`
	EndDate   *Date `json:"endDate"`
	// DescriptionCode is document level only (UNTDID 2005 tax point date code).
	DescriptionCode *string `json:"descriptionCode"`
}

InvoicePeriod is an invoicing period, at document level (BG-14) or line level (BG-26).

type InvoiceRecord

type InvoiceRecord struct {
	ID     string        `json:"id"`
	Status InvoiceStatus `json:"status"`

	Invoice *Invoice `json:"canonicalJson"`

	// ValidationResult is nil while Status is StatusProcessing.
	ValidationResult *ValidationResult `json:"validationResult"`

	CreatedAt         time.Time `json:"createdAt"`
	ValidationProfile string    `json:"validationProfile"`
	InvoiceFormat     string    `json:"invoiceFormat"`
	UploadedBy        string    `json:"uploadedBy"`
	Sandbox           bool      `json:"sandbox"`
	APIKeyID          *string   `json:"apiKeyId"`
}

InvoiceRecord is the envelope the backend returns for a submitted invoice: submission metadata plus the parsed Invoice and, once validation has finished, its ValidationResult. Distinct from Invoice itself because GET /v1/invoices/{id} never returns a bare invoice — the backend column backing this field is named canonicalJson, remapped here to Invoice so the SDK's public field name is meaningful rather than leaking an internal storage detail.

type InvoiceStatus

type InvoiceStatus string

InvoiceStatus is the lifecycle state of a submitted invoice.

const (
	StatusProcessing       InvoiceStatus = "processing"
	StatusValidated        InvoiceStatus = "validated"
	StatusFailedValidation InvoiceStatus = "failed_validation"
)

func (InvoiceStatus) IsTerminal

func (s InvoiceStatus) IsTerminal() bool

IsTerminal reports whether s is a terminal status — VALIDATED and FAILED_VALIDATION are both terminal, successful outcomes from the SDK's perspective (a failed validation is still a completed API call).

type InvoiceSubmission

type InvoiceSubmission struct {
	InvoiceNumber string `json:"invoiceNumber,omitempty"`
	IssueDate     Date   `json:"issueDate,omitempty"`
	Currency      string `json:"currency,omitempty"`
	// BuyerReference is optional in general, mandatory in practice for XRechnung (Leitweg-ID).
	BuyerReference    string                       `json:"buyerReference,omitempty"`
	ValidationProfile ValidationProfile            `json:"validationProfile,omitempty"`
	Supplier          InvoiceSubmissionPartyInput  `json:"supplier"`
	Buyer             InvoiceSubmissionPartyInput  `json:"buyer"`
	Lines             []InvoiceSubmissionLineInput `json:"lines,omitempty"`
}

InvoiceSubmission is the request body for InvoicesService.Submit. Mirrors the backend's flat InvoiceRequest shape — deliberately not the same type as the much richer Invoice read model, since submit and read are genuinely different contracts.

type InvoiceSubmissionLineInput

type InvoiceSubmissionLineInput struct {
	Description string `json:"description,omitempty"`
	Quantity    Number `json:"quantity,omitempty"`
	Unit        string `json:"unit,omitempty"`
	Price       Number `json:"price,omitempty"`
	TaxRate     Number `json:"taxRate,omitempty"`
}

InvoiceSubmissionLineInput is a single invoice line, as submitted with a new invoice.

type InvoiceSubmissionPartyInput

type InvoiceSubmissionPartyInput struct {
	Name      string `json:"name,omitempty"`
	VatID     string `json:"vatId,omitempty"`
	LeitwegID string `json:"leitwegId,omitempty"`
}

InvoiceSubmissionPartyInput is a seller or buyer, as submitted with a new invoice.

type InvoiceSubmissionResult

type InvoiceSubmissionResult struct {
	ID     string        `json:"id"`
	Status InvoiceStatus `json:"status"`
}

InvoiceSubmissionResult is the 202 Accepted response every submit/validate endpoint returns.

type InvoicesService

type InvoicesService struct {
	// contains filtered or unexported fields
}

InvoicesService submits invoices for validation and retrieves results. Reachable via Client.Invoices.

func (*InvoicesService) DownloadIdocXML

func (s *InvoicesService) DownloadIdocXML(ctx context.Context, id string) ([]byte, error)

DownloadIdocXML exports a validated invoice as a SAP IDoc XML file.

func (*InvoicesService) DownloadPDF

func (s *InvoicesService) DownloadPDF(ctx context.Context, id string) ([]byte, error)

DownloadPDF exports a validated invoice as a ZUGFeRD-compliant PDF with embedded XML.

func (*InvoicesService) Get

Get retrieves a single invoice by ID.

func (*InvoicesService) List

func (s *InvoicesService) List(ctx context.Context, page, size int, sort string) (Page[InvoiceRecord], error)

List retrieves one page of invoices. sort matches the backend's default "createdAt,desc" when empty.

func (*InvoicesService) ListAll

func (s *InvoicesService) ListAll(ctx context.Context, pageSize int) iter.Seq2[InvoiceRecord, error]

ListAll lazily iterates every invoice across all pages, fetching the next page only once the current one is exhausted. Stop the range early (break) to avoid fetching further pages.

for record, err := range client.Invoices.ListAll(ctx, 50) {
    if err != nil { ... }
}

func (*InvoicesService) Submit

Submit queues a structured JSON invoice for async validation. Poll Get or use SubmitAndWait for the result.

func (*InvoicesService) SubmitAndWait

func (s *InvoicesService) SubmitAndWait(ctx context.Context, submission InvoiceSubmission, opts PollOptions) (InvoiceRecord, error)

SubmitAndWait submits submission and polls Get until validation reaches a terminal status.

func (*InvoicesService) SubmitOData

func (s *InvoicesService) SubmitOData(ctx context.Context, rawJSON []byte, profile ValidationProfile) (InvoiceSubmissionResult, error)

SubmitOData submits a raw SAP S/4HANA OData supplier-invoice JSON payload for validation. Ships as a thin passthrough in v1 rather than a fully typed model — the payload is large and SAP-integration specific.

func (*InvoicesService) SubmitODataMap

func (s *InvoicesService) SubmitODataMap(ctx context.Context, payload map[string]any, profile ValidationProfile) (InvoiceSubmissionResult, error)

SubmitODataMap is the same as SubmitOData, for callers building the payload as a map instead of raw JSON.

func (*InvoicesService) ValidateFile

func (s *InvoicesService) ValidateFile(ctx context.Context, path string, profile ValidationProfile) (InvoiceSubmissionResult, error)

ValidateFile uploads a UBL XML, CII XML, or ZUGFeRD PDF file for validation. Processing is asynchronous — poll Get or use ValidateFileAndWait for the result.

func (*InvoicesService) ValidateFileAndWait

func (s *InvoicesService) ValidateFileAndWait(ctx context.Context, path string, profile ValidationProfile, opts PollOptions) (InvoiceRecord, error)

ValidateFileAndWait uploads path and polls Get until validation reaches a terminal status.

func (*InvoicesService) ValidateFileBytes

func (s *InvoicesService) ValidateFileBytes(ctx context.Context, filename string, content []byte, profile ValidationProfile) (InvoiceSubmissionResult, error)

ValidateFileBytes is the same as ValidateFile, for callers who already hold the file bytes in memory.

func (*InvoicesService) ValidateFileBytesAndWait

func (s *InvoicesService) ValidateFileBytesAndWait(ctx context.Context, filename string, content []byte, profile ValidationProfile, opts PollOptions) (InvoiceRecord, error)

ValidateFileBytesAndWait is the same as ValidateFileAndWait, for in-memory file bytes.

func (*InvoicesService) ValidateIdoc

func (s *InvoicesService) ValidateIdoc(ctx context.Context, path string, profile ValidationProfile) (InvoiceSubmissionResult, error)

ValidateIdoc uploads a SAP IDoc XML file for validation. Processing is asynchronous — poll Get or use ValidateIdocAndWait.

func (*InvoicesService) ValidateIdocAndWait

func (s *InvoicesService) ValidateIdocAndWait(ctx context.Context, path string, profile ValidationProfile, opts PollOptions) (InvoiceRecord, error)

ValidateIdocAndWait uploads path as a SAP IDoc and polls Get until validation reaches a terminal status.

func (*InvoicesService) ValidateIdocBytes

func (s *InvoicesService) ValidateIdocBytes(ctx context.Context, filename string, content []byte, profile ValidationProfile) (InvoiceSubmissionResult, error)

ValidateIdocBytes is the same as ValidateIdoc, for callers who already hold the file bytes in memory.

func (*InvoicesService) ValidateIdocBytesAndWait

func (s *InvoicesService) ValidateIdocBytesAndWait(ctx context.Context, filename string, content []byte, profile ValidationProfile, opts PollOptions) (InvoiceRecord, error)

ValidateIdocBytesAndWait is the same as ValidateIdocAndWait, for in-memory file bytes.

type ItemAttribute

type ItemAttribute struct {
	Name  *string `json:"name"`
	Value *string `json:"value"`
}

type ItemClassification

type ItemClassification struct {
	Code *string `json:"code"`
	// ListID is the UNTDID 7143 scheme identifier.
	ListID        *string `json:"listId"`
	ListVersionID *string `json:"listVersionId"`
}

type KositResult

type KositResult struct {
	Valid  bool     `json:"valid"`
	Errors []string `json:"errors"`
}

type LineAllowanceCharge

type LineAllowanceCharge struct {
	Charge     bool    `json:"charge"`
	Amount     *Number `json:"amount"`
	BaseAmount *Number `json:"baseAmount"`
	Percentage *Number `json:"percentage"`
	Reason     *string `json:"reason"`
	ReasonCode *string `json:"reasonCode"`
}

LineAllowanceCharge is a line-level allowance/charge (BT-136..BT-141).

type NotFoundError

type NotFoundError struct{ *APIError }

NotFoundError is a 404 — the requested resource does not exist.

type Number

type Number = json.Number

Number is an arbitrary-precision decimal literal, used for every monetary and quantity field.

It is a type alias (not a new type) for encoding/json.Number, so any json.Number value works interchangeably with xingen.Number and callers don't need to import encoding/json themselves. Struct fields typed as (*)json.Number decode straight from the source-text numeric literal — confirmed empirically — with no float64 intermediate and no custom parser required, unlike the Python/TypeScript/PHP ports of this SDK, which each needed a hand-rolled numeric-JSON parser to avoid silently corrupting high-precision monetary values.

type Option

type Option func(*clientConfig)

Option configures a Client constructed by New.

func WithBaseURL

func WithBaseURL(baseURL string) Option

WithBaseURL overrides the default https://app.xingen.de/api base URL — useful for self-hosted or local testing.

func WithConnectTimeout

func WithConnectTimeout(d time.Duration) Option

WithConnectTimeout sets the TCP connect timeout. Ignored if WithHTTPClient is also passed.

func WithHTTPClient

func WithHTTPClient(httpClient *http.Client) Option

WithHTTPClient overrides the *http.Client used for every request. When set, WithConnectTimeout has no effect — configure the dialer's timeout on the supplied client instead.

func WithRequestTimeout

func WithRequestTimeout(d time.Duration) Option

WithRequestTimeout sets the per-request timeout, applied to every call the SDK makes (not the total time of a *AndWait polling helper — see PollOptions.Timeout for that).

type Page

type Page[T any] struct {
	Content          []T   `json:"content"`
	TotalElements    int64 `json:"totalElements"`
	TotalPages       int   `json:"totalPages"`
	Number           int   `json:"number"`
	Size             int   `json:"size"`
	First            bool  `json:"first"`
	Last             bool  `json:"last"`
	NumberOfElements int   `json:"numberOfElements"`
	Empty            bool  `json:"empty"`
}

Page mirrors the JSON shape Spring Data's Page<T> serializes to.

type Party

type Party struct {
	Name                      *string           `json:"name"`
	RegistrationName          *string           `json:"registrationName"`
	VatID                     *string           `json:"vatId"`
	TaxRegistrationID         *string           `json:"taxRegistrationId"`
	LegalRegistrationID       *string           `json:"legalRegistrationId"`
	LegalRegistrationSchemeID *string           `json:"legalRegistrationSchemeId"`
	AdditionalLegalInfo       *string           `json:"additionalLegalInfo"`
	LeitwegID                 *string           `json:"leitwegId"`
	EndpointID                *string           `json:"endpointId"`
	EndpointSchemeID          *string           `json:"endpointSchemeId"`
	Identifiers               []PartyIdentifier `json:"identifiers"`
	// Address is nil iff no postal address element was present in the source document.
	Address *Address `json:"address"`
	// Contact is nil iff no contact element was present in the source document.
	Contact *Contact `json:"contact"`
}

type PartyIdentifier

type PartyIdentifier struct {
	ID *string `json:"id"`
	// SchemeID is the ISO 6523 ICD, or "SEPA" for creditor identifiers.
	SchemeID *string `json:"schemeId"`
}

type PaymentMeans

type PaymentMeans struct {
	TypeCode                *string `json:"typeCode"`
	PaymentMeansText        *string `json:"paymentMeansText"`
	RemittanceInformation   *string `json:"remittanceInformation"`
	CreditTransferAccountID *string `json:"creditTransferAccountId"`
	AccountName             *string `json:"accountName"`
	ServiceProviderID       *string `json:"serviceProviderId"`
	MandateReferenceID      *string `json:"mandateReferenceId"`
	CardAccountNumber       *string `json:"cardAccountNumber"`
	CardHolderName          *string `json:"cardHolderName"`
	CreditorID              *string `json:"creditorId"`
	DebitedAccountID        *string `json:"debitedAccountId"`
}

type PermissionError

type PermissionError struct{ *APIError }

PermissionError is a 403 — e.g. the requested invoice exists but is not owned by the caller.

type PollCancelledError

type PollCancelledError struct {
	Message string
}

PollCancelledError is returned by a *AndWait polling helper when the context passed to it is cancelled before the invoice reaches a terminal status.

func (*PollCancelledError) Error

func (e *PollCancelledError) Error() string

type PollOptions

type PollOptions struct {
	InitialInterval   time.Duration
	MaxInterval       time.Duration
	BackoffMultiplier float64
	// Timeout is the total time budget across the whole poll loop, not a per-request timeout.
	Timeout time.Duration
}

PollOptions configures the polling loop used by the *AndWait helpers on InvoicesService.

func DefaultPollOptions

func DefaultPollOptions() PollOptions

DefaultPollOptions returns the default backoff schedule: 500ms initial interval, 1.5x multiplier, capped at 5s, with a 60s overall timeout.

type PollTimeoutError

type PollTimeoutError struct {
	Message       string
	PartialResult *InvoiceRecord
}

PollTimeoutError is returned by a *AndWait polling helper when PollOptions.Timeout elapses before the invoice reaches a terminal status. PartialResult holds the last known state.

func (*PollTimeoutError) Error

func (e *PollTimeoutError) Error() string

type PrecedingInvoiceReference

type PrecedingInvoiceReference struct {
	ID        *string `json:"id"`
	IssueDate *Date   `json:"issueDate"`
}

type QuotaExceededError

type QuotaExceededError struct{ *APIError }

QuotaExceededError is a 429 — the API key's request quota has been exhausted. This bypasses the backend's normal error pipeline, so the body is {"error": "..."}, not the standard ErrorResponse shape.

type Severity

type Severity string

Severity is how serious a ValidationError is.

const (
	SeverityError   Severity = "ERROR"
	SeverityWarning Severity = "WARNING"
	SeverityInfo    Severity = "INFO"
)

type SupportingDocument

type SupportingDocument struct {
	ID       *string `json:"id"`
	SchemeID *string `json:"schemeId"`
	// TypeCode is the UNTDID 1001 document type code, e.g. "50", "130".
	TypeCode    *string `json:"typeCode"`
	Description *string `json:"description"`
	// ExternalURI is nil for no external-reference element; empty string for present but the URI missing.
	ExternalURI     *string `json:"externalUri"`
	MimeCode        *string `json:"mimeCode"`
	Filename        *string `json:"filename"`
	EmbeddedPresent bool    `json:"embeddedPresent"`
}

type TaxBreakdown

type TaxBreakdown struct {
	TaxableAmount *Number `json:"taxableAmount"`
	TaxAmount     *Number `json:"taxAmount"`
	// CategoryCode is one of S / Z / E / AE / K / G / O.
	CategoryCode *string `json:"categoryCode"`
	// Rate is nil for exempt categories (E/AE/K/G/O).
	Rate                *Number `json:"rate"`
	ExemptionReason     *string `json:"exemptionReason"`
	ExemptionReasonCode *string `json:"exemptionReasonCode"`
}

type TransportError

type TransportError struct {
	Message string
	Err     error
}

TransportError wraps a network-level failure (connection refused, DNS failure, context cancellation, etc.) that happened before an HTTP response was received — not an HTTP error response.

func (*TransportError) Error

func (e *TransportError) Error() string

func (*TransportError) Unwrap

func (e *TransportError) Unwrap() error

type ValidationError

type ValidationError struct {
	Code             *string          `json:"code"`
	Message          *string          `json:"message"`
	Field            *string          `json:"field"`
	Suggestion       *string          `json:"suggestion"`
	DocumentationURL *string          `json:"documentationUrl"`
	Layer            *ValidationLayer `json:"layer"`
	Severity         *Severity        `json:"severity"`
}

type ValidationLayer

type ValidationLayer string

ValidationLayer identifies which rule set a ValidationError came from.

const (
	// LayerCore is EN16931.
	LayerCore ValidationLayer = "CORE"
	// LayerNational is XRechnung.
	LayerNational ValidationLayer = "NATIONAL"
	// LayerNetwork is Peppol.
	LayerNetwork ValidationLayer = "NETWORK"
)

type ValidationProfile

type ValidationProfile string

ValidationProfile selects which standard an invoice is validated against.

  • EN16931 — European standard EN 16931 (free)
  • PEPPOL — PEPPOL BIS Billing 3.0 (free)
  • XRECHNUNG — German XRechnung standard (Pro)
  • FRANCE — French Factur-X standard (Pro) — not yet implemented server-side
  • ITALY — Italian FatturaPA standard (Pro) — not yet implemented server-side
const (
	ProfileEN16931   ValidationProfile = "EN16931"
	ProfilePEPPOL    ValidationProfile = "PEPPOL"
	ProfileXRechnung ValidationProfile = "XRECHNUNG"
	ProfileFrance    ValidationProfile = "FRANCE"
	ProfileItaly     ValidationProfile = "ITALY"
)

type ValidationRequestError

type ValidationRequestError struct {
	*APIError
	// FieldErrors is nil unless the failure was a bean-validation error on a request body (as
	// opposed to a plain malformed-request 400).
	FieldErrors map[string]string
}

ValidationRequestError is a 400 — malformed request body or bean-validation failure.

type ValidationResult

type ValidationResult struct {
	Valid  bool              `json:"valid"`
	Errors []ValidationError `json:"errors"`
	// KositResult is only populated for XML-based validation paths (UBL/CII/IDoc). Nil otherwise.
	KositResult *KositResult `json:"kositResult"`
}

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL