Skip to main content

LifecycleMessageLoad

Read and write the timeline of events on an invoice. The conceptual page is Concepts → Lifecycle messages — read that for the state machine, code catalog, and write-vs-read overview. This page is the field-level reference for the Load class.

Import

from lib.objects.lifecycle_message import LifecycleMessageLoad

Write — new() + post_to() / post_by_reference()

Build a message in memory, set its fields, then post to either the invoice token (preferred) or the external invoice_number+supplier_code reference.

load = LifecycleMessageLoad(context)

msg = load.new()
msg.code = 'ACCEPTED' # required
msg.reference_type = 'PAYMENT_REF'
msg.reference_value = 'ERP-INV-12345'
msg.note_supplier = 'Invoice posted to AP queue.'
msg.note_internal = 'ERP batch 2026-05-17-001'

resp = msg.post_to(
invoice_token='7f3a...',
idempotency_key='erp-accept-12345', # optional but recommended
)
FieldTypeRequiredNotes
codestryesAny valid lifecycle code from the code catalog. All 28 codes are externally writable; the state machine enforces ordering. The SDK logs a warning if you set a code that isn't in its catalog (typo guard).
occurred_atstr (ISO-8601)noDefaults to NOW server-side. Must be within 30 days of current time.
actor_display_namestrnoDefaults to the API user's name
clarification_codestrnoPer-customer config can make this required for specific codes
note_internalstrnoMax 4000 chars. AP-team-only — not visible to suppliers
note_supplierstrnoMax 4000 chars. Visible to suppliers in the supplier portal AND the AP team
reference_typestrnoOne of PO_NUMBER, PAYMENT_REF, CREDIT_NOTE, OTHER
reference_valuestrnoFree-form id from the external system

Local validation runs in post_to() / post_by_reference() before the HTTP call — invalid reference_type, oversized notes, missing code all raise ValueError before any network round-trip.

post_by_reference() — when you don't have the invoice token

resp = msg.post_by_reference(
invoice_number='INV-2026-0042',
supplier_code='SUP-100',
supplier_location_code='MAIN', # optional, narrows the match
erp_company_code='EMEA', # optional
idempotency_key='pmt-77-3-paid',
)

If the (invoice_number, supplier_code) pair matches more than one invoice, the server returns 400 — pass supplier_location_code and/or erp_company_code until you narrow to one match.

Read — get_for_invoice()

Returns the full ordered timeline for one invoice as a list of LifecycleMessage objects.

for m in load.get_for_invoice('7f3a...'):
print(m.recorded_at, m.code, m.note_supplier)

Optional args:

ArgNotes
tier1 (intake), 2 (buyer-side), 3 (financial), or None (all)
include_internal_notesRequires LIFECYCLE_READ_INTERNAL on the API user. When True without the permission, internal notes are silently omitted (no error).

Read — poll_since() (cross-invoice delta)

For "what changed across many invoices since I last polled". Auto-paginates through results — you get a generator.

for m in load.poll_since(
since='2026-04-25T08:00:00Z', # ISO-8601 UTC
ext_references={1: 'CUSTOMER-A'}, # at least one ext_reference_N required
tz='UTC', # only consulted when `since` is naked
tier=None, # 1, 2, 3, or None for all
include_internal_notes=False,
page_size=500, # default 500, max 5000
):
print(m.invoice_token, m.recorded_at, m.code)

Watermark for next poll

Messages come back ordered by recorded_at ASC. Store the recorded_at of the last message you saw, and pass it as since on the next call. Use the delta state helpers to manage the watermark across runs without writing the bookkeeping yourself.

ext_references filter

Required — at least one of keys 1..5 must be a non-empty string. Comparison is case-insensitive equality — wildcards are treated literally. Use it to scope per customer / per ERP / per BU according to how you've populated ext_reference_1..5 on the invoice side.

LifecycleMessage (read shape)

Returned by both get_for_invoice() and poll_since(). Attribute names mirror the API response.

FieldNotes
idint — internal message id
invoice_idint (from delta endpoint) or token string (from per-invoice endpoint)
invoice_tokenstr (UUID) — always populated when available
codestr — the lifecycle event code
sourcestr — NUNTIQ, EXTERNAL_API, or MANUAL_USER
actor_typestr — SYSTEM, API_CLIENT, or USER
actor_idstr (UUID) or None
actor_display_namestr or None
occurred_atstr (ISO-8601) — when the event actually happened
recorded_atstr (ISO-8601) — when Nuntiq learned about the event
clarification_codestr or None
note_internalstr or None (gated on permission)
note_supplierstr or None
reference_typestr or None
reference_valuestr or None
ext_reference_1..5str or None — only populated when returned by poll_since() (carried from the parent invoice)

The difference between occurred_at and recorded_at: when an upstream system posts a back-dated event ("this was approved last Tuesday"), occurred_at is Tuesday but recorded_at is now. Always page on recorded_at for poll watermarks; sort/display on occurred_at.

Common pitfalls

Most failure modes are documented on Concepts → Lifecycle messages → Common pitfalls. Quick recap:

Server responseCause
400 INVALID_CODETypo / unknown code string. Check against LIFECYCLE_CODES or the catalog.
400 NOTE_REQUIRED / CLARIFICATION_CODE_REQUIREDTenant config requires the field
422 INVOICE_NOT_READYPosting before intake finished (status < 90)
409 LIFECYCLE_TRANSITION_INVALIDState machine refused. Read valid_next_codes.
409 TERMINAL_STATEInvoice is at REJECTED or CANCELLED
200 with idempotent: trueRe-post deduped by idempotency_key

What's next

  • Concepts → Lifecycle messages — the full conceptual write-up including the code catalog and state machine.
  • InvoiceLoad — the read/claim/ack/result flow for the invoices these messages are written against.