Toolkit overview
The toolkit is a self-contained zip. Once unzipped, this is what you'll find:
nuntiq-connector-toolkit-<version>/
├── nuntiq.py <- entrypoint you run
├── AGENTS.md <- the whole picture for AI coding tools (and humans)
├── README.md
├── requirements.txt
│
├── settings/ <- one .json per connector instance
│ ├── example.json <- template; copy to <your_instance>.json (gitignored)
│ └── ...
│
├── connectors/ <- your code goes here: one file per entrypoint, shared_<name>.py = shared module
│ ├── 01_use_settings.py
│ ├── 03_pull_invoices.py
│ ├── 04_poll_lifecycle_messages.py
│ ├── 06_export_invoices_csv.py
│ ├── get_custom_1.py
│ ├── get_fx_rates.py
│ ├── get_supplier.py
│ └── push_invoice.py
│
├── templates/ <- 21 ready connectors, one folder each (README.md, settings.example.json)
│
├── fixtures/ <- dummy data tables (JSON, edit freely)
│ ├── supplier.json
│ ├── invoice.json
│ ├── user.json
│ ├── ...
│ ├── _seed/ <- canonical state used by `r` (reset)
│ ├── attachment_files/ <- raw bytes served by download_attachment()
│ └── sftp/ <- create it: the SFTP home folder of every run (context.files())
│
├── docs/ <- SDK reference (also on this site)
│
├── lib/ <- the SDK you import in your connectors
│ ├── helpers/ <- Settings, HttpClient, logins, SFTP/S3 files, run_push, ...
│ ├── objects/ <- SupplierLoad, InvoiceLoad, IngestionLoad, ...
│ ├── api_client.py <- ApiError (the client itself is injected at runtime)
│ ├── delta.py
│ └── work_dir.py
│
└── _toolkit/ <- sandbox internals (you can ignore this)
├── runner.py
├── runtime.py
├── fake_api_client.py
├── fake_engine.py
└── json_store.py
The pieces
connectors/: your code
One .py per entrypoint. The file name is the entrypoint key: a file named
after a standard key (get_supplier.py, push_invoice.py, get_custom_1.py)
runs with that key's permissions, as in production. A file with any other
name may only read invoices. Eight examples come with the toolkit:
| File | Pattern | Used by |
|---|---|---|
01_use_settings.py | Diagnostic: logs the settings it finds | Checking config wiring |
03_pull_invoices.py | Inbound delta sync, numeric_id cursor | Pulling invoices into Nuntiq |
04_poll_lifecycle_messages.py | Outbound lifecycle event poll | Telling external systems about status changes |
06_export_invoices_csv.py | Read-only export with attachments | Reporting, archival, ad-hoc exports |
get_custom_1.py | Full sync run into a customer data table | Cost centres and other reference lists |
get_fx_rates.py | Exchange rates with a date cursor and a restate option | The customer's own exchange rates |
get_supplier.py | Delta sync or full sync run, patch_all, delete_keys | Master-data sync (suppliers, organizations, POs) |
push_invoice.py | Process engine call or claim with leases, then results | Posting invoices to a customer's ERP |
Read them. They are commented and cover every framework primitive you will touch.
templates/: ready connectors
One folder per connector template: every entrypoint, the shared
modules, a README.md with every setting and secret, and a
settings.example.json. Copy that file to settings/ and run the template
in place (its connector_dir points at the folder), or copy the folder and
adapt your copy.
fixtures/: your fake Nuntiq
Each *.json file represents one table. The fake API loads them into memory on
startup and reads/writes against the in-memory copy. Edit freely; the next run
picks up your changes.
[
{
"supplier_number": "SUP-001",
"supplier_name": "Acme Corp",
"is_active": true,
"locations": [
{"location_code": "MAIN", "addresses": [...]}
]
}
]
Press r at the menu to reset everything from fixtures/_seed/. Run a
destructive entrypoint, then r to start clean.
SFTP files locally
The folder fixtures/sftp/ is the home folder of the
SFTP user of every local run.
Create fixtures/sftp/in/ and put a file there, and
context.files().list('in') lists it; what your code writes, moves or
deletes happens in that folder. backup() moves the file to
fixtures/sftp_backup/. Without fixtures/sftp/, context.files() raises
ConfigError, as a run without an SFTP user does in production.
Locally there is no malware scan and no file type list; the 50 MB limit of
read_bytes and write_bytes applies as in production. Test the scan wait
and the SFTP user's file types on a real environment. r leaves
fixtures/sftp/ as it is.
lib/: the SDK
This is identical to what production has. Same module paths, same class names, same method signatures. Imports you write here continue to work when your code is deployed:
from lib.objects.supplier import SupplierLoad # same in prod
from lib.helpers import Settings, run_push # same in prod
You never edit lib/. The toolkit ships the SDK frozen at the production
version it was built against.
_toolkit/: sandbox internals
The fake API client, JSON store, and local runner. You can ignore this folder: the fact that production uses HTTP and the toolkit uses dicts is invisible to your code.
What the toolkit simulates
- The full
JobContextshape and every method. - Token operation scoping: a run may only call the operations its entrypoint key allows; anything else gets a 403, as in production.
- The integration engine: keys, full records on POST, PATCH merges, delete by
key, ownership by
config.source_system/config.ownership_mode, per-record results, cursor reads and sync runs. - Invoice leases: claim, re-offer after the TTL,
lease_token_requiredandlease_mismatch, release and the bulk calls. Idempotency-Keyreplay, payment merges, and the file type and duplicate checks on uploads.- Delta state (
delta_run,delta_get_state,delta_begin, ...). - Attachment downloads (
fixtures/attachment_files/{token}.bin→ bytes). - Nuntiq SFTP files (
context.files()overfixtures/sftp/), without the malware scan and the file type checks. - Logger output (the same text format as production).
What it doesn't simulate
- File processing.
ingest_pdf/upload_source_documentscheck the file type and duplicates and store a source document, but nothing is OCR-ed and no invoice comes out of it.invoice_importstores a minimal invoice you can claim afterwards, without template matching or enrichment.upload_files('einvoice', ...)stores inbound upload rows; nothing is mapped. - Lifecycle status validation. The fake accepts more transitions on
integration_resultand lifecycle posts than production does. Always test the posting loop on a real environment before releasing. - Change history, organization-scoped keys, webhooks, e-mails.
- Queues, scheduling, rate limits, external HTTP. The toolkit makes no outbound requests except the ones your code makes.
- Lifecycle delta pagination. Returns one page, no
next_cursor. - Multi-tenant isolation.
customer_numberis informational only; your one fixture folder is the only tenant.
When to leave the toolkit
The toolkit catches typos, wrong field names, broken control flow, missing imports, type errors, and most logic bugs. It is not a substitute for an end-to-end test against dev before shipping a connector.
The deploy flow when you're ready:
- Paste
connectors/<key>.pyinto the connector template (or the customer connector) in the admin portal, under the same entrypoint key, and everyshared_<name>.pyon the Shared code tab. - Enable the entrypoint on the customer connector.
- Run it once through a scheduler task (
connector_dispatch). - Read the run's log in the task history.
The full setup is in the Guide for coding assistants.
What's next
- Concepts → Architecture: what actually happens when your code runs in production.
- Concepts → JobContext: the
contextargument in depth.