Skip to main content

Downloads

Nuntiq Connector Toolkit​

The toolkit is a self-contained Python sandbox for writing and testing connectors on your own machine. Everything runs against in-memory dummy data backed by JSON files, so there is no Nuntiq account, no network and no AWS involved. Your connector talks to your real external system while Nuntiq is simulated.

nuntiq-connector-toolkit-0.6.4.zip

v0.6.4 · ~700 KB · Python 3.10+

Download zip

SHA-256: 7e34f2e47cbd0d82b11ce67c265cd4de9394237ec1ab2995282f3b326ca19284

Verify after download:

(Get-FileHash nuntiq-connector-toolkit-0.6.4.zip -Algorithm SHA256).Hash.ToLower()

What's inside​

nuntiq-connector-toolkit-0.6.4/
├── 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 is the template)
├── connectors/ <- 8 example entrypoints + your code: one file per entrypoint, shared_<name>.py = shared module
├── templates/ <- 21 ready connectors (ERP over REST, SOAP and files; invoices in; Zoho Books)
├── fixtures/ <- dummy data tables (JSON, edit freely); sftp/ = the run's SFTP home folder
├── docs/ <- SDK reference (also on this site)
├── lib/ <- the SDK you import (lib/helpers: the building blocks of the templates)
└── _toolkit/ <- sandbox internals (you can ignore)

See Toolkit overview for what each folder does, and Guide for coding assistants for the text of AGENTS.md: the production runtime contract, where every context value comes from, the entrypoint keys, and how a connector is set up in the admin portal.

Quick start​

  1. Unzip somewhere convenient.
  2. python -m venv .venv && .\.venv\Scripts\activate (Windows) or source .venv/bin/activate (macOS/Linux).
  3. pip install -r requirements.txt.
  4. cp settings/example.json settings/my_instance.json and edit it, or copy a template's templates/<key>/settings.example.json.
  5. python nuntiq.py (menus) or python nuntiq.py my_instance get_supplier (one run).

Full walkthrough: Installation → Your first connector.

Changelog​

0.6.4 (2026-11-04)​

  • Nuntiq SFTP files, one SFTP user per run: a scheduler task or an integration rule names at most one Data Integration SFTP user, and context.files() (no argument) returns that user's whole home folder, read and write. This replaces the named mounts of 0.6.3: mount names, subfolders, read-only access and the _mounts declaration are gone. A call with a mount name now raises TypeError; drop the argument. Without a usable SFTP user context.files() raises ConfigError with the reason. See Nuntiq SFTP files.
  • In the sandbox fixtures/sftp/ itself is the SFTP home folder (no folder per mount). Move files from fixtures/sftp/<name>/ up one level. See SFTP files locally.

0.6.3 (2026-11-04)​

  • Nuntiq SFTP mounts: context.files('<mount>') returns a NuntiqFileStore over a folder of a Data Integration SFTP user on Nuntiq's SFTP server, bound on the integration rule (tab SFTP Mounts) or the scheduler task (Connector Settings). The same methods as SftpStore and S3Store (so Inbox and Outbox work on it), plus stat, list_folders, copy, rmdir and backup. Entrypoints declare the mounts they need in their configuration (_mounts). See Nuntiq SFTP files.
  • In the sandbox every folder under fixtures/sftp/<name>/ is a read-and-write mount of the run. See SFTP files locally.
  • requirements.txt: authlib 1.9.2.

0.6.2 (2026-10-23)​

  • Record ownership, four modes: the sandbox follows the API's ownership_mode (in the settings' config): system_of_record (all records), all_integrated (all records that came from an integration), managed (the default: records with your source system, and records entered in Nuntiq may be taken over by updating them) and own_only (only records with your source system). A write or delete outside the mode is refused with owned_by_other_source.
  • The source system travels in the records, not on the URL (as the API since 2026-10-22). The SDK puts the connector's source system in every record that carries its own source record id and names none; a record found by its id or natural key keeps the source system of the stored row, so an update never moves another system's record to your connector. Another source system in a record is accepted only under system_of_record or all_integrated. A source_system on the URL that differs from the connector's is refused (source_system_override_not_allowed).
  • Claiming invoices needs write_invoice or lease_invoice, as in the API.

0.6.1 (2026-10-14)​

  • Invoice deductions and amount due in the SDK: invoice.deductions (InvoiceDeduction: type retainage, withholding, deposit or other, description, rate, base amount, amount) and invoice.amount_due on an invoice read from the API; new_deduction(type) and header.amount_due on a structured import (IngestionDeduction); InvoiceCharge is exported. The sandbox returns and stores them; invoice 5003 in the fixtures has a 5% retainage. See Invoice.
  • Templates 1.1.0: every template that pushes, exports or ingests invoices carries charges, discounts, deductions and the amount due, with a deductions_mode setting on the push templates. See Connector templates.
  • inbound_rest_oauth2_refresh_token keeps the access token between runs.
  • requirements.txt in step with the connector base image of 2026-10-12 (paramiko 5.0.0: no SHA-1 in SSH).

0.6.0 (2026-10-11)​

  • Connector templates in templates/: 21 complete connectors, one folder each with every entrypoint, the shared modules, a README.md and a settings.example.json. ERP over REST (API key, Basic, OAuth 2 client credentials, refresh token, JWT, OAuth 1 TBA, mTLS, session), ERP over SOAP, ERP over files (SFTP with password, key or certificate; S3), invoices in over REST, SFTP and S3, and Zoho Books. See Connector templates.
  • Helpers in lib/helpers: typed settings, an HTTP client with retries, re-login and paging, every login method, SFTP, S3 and local file stores with inboxes and atomic writes, PGP, CSV, Excel and safe XML reading, the delta cursor around a pull, the whole push_invoice (run_push), the status run for async pushes (ResultReporter) and invoice intake. See Helpers.
  • A settings file may name a connector_dir: the runner uses that folder's entrypoints instead of connectors/ (the templates' settings files do).
  • python nuntiq.py <settings> <entrypoint> runs one entrypoint without the menus; the exit code is 1 when the run fails.
  • A handler that returns success: false is reported as failed, as in production.
  • Acknowledging an invoice stores external ids and messages, an engine invoice can be acknowledged (it waits on its integration step), and the invoice list returns the lease to the connector that holds it. See Async push and status.
  • requirements.txt in step with production: requests 2.32.5, authlib 1.6.5, msal, boto3, cryptography, defusedxml, python-gnupg added. PGP needs the gpg program on your PC.
  • AGENTS.md rewritten around the templates and the helpers.

0.5.4 (2026-10-11)​

  • AGENTS.md: every connector run has 4 GB of disk; handle files one at a time (download, send, delete) instead of collecting them first, and page large pulls over several runs with the delta cursor. A run that goes over the limit fails with "The run ran out of disk space (limit 4g per run)".

0.5.3 (2026-10-11)​

  • context.api.upload_files(process, files, ...): send files with a role each. 'capture' (one source document): 'document' is captured, 'attachment' is kept with the source document and attached to its invoices (a PDF too). 'einvoice' (one batch): 'data' files are mapped by the customer's file formats, 'attachment' PDFs and images are linked to the invoice that names them. A missing or wrong role, or a file whose bytes do not fit its role, raises ValueError before anything is sent. See Sending files.
  • from lib.file_types import classify_file: what a file is, read from its bytes exactly as the API does. The sandbox's own type checks use it.
  • upload_source_documents() and upload_inbound_files() call upload_files(); next to the PDFs, images, Office documents, CSV, TXT, XML, JSON, EML and MSG files are kept as attachments, and an archive inside a ZIP is refused with 415.
  • The sandbox stores e-invoice files in fixtures/inbound_upload.json (nothing is mapped) and source document files with their role.
  • 0.5.2 was not published; its changes are part of 0.5.3.

0.5.1 (2026-10-11)​

  • context.update_secret(name, value) and context.update_secrets({...}): store a secret the connector rotated itself, such as an OAuth refresh token. Only names in the settings file's rotatable_secrets are accepted (in production: Secrets the connector may update on the connector); others raise SecretUpdateError. The sandbox writes the value into the secrets section of the settings file in use and reads the file again before every run. See OAuth 2.0.

0.5.0 (2026-10-02)​

  • The SDK and the sandbox follow the integration API contract. Supplier, organization, purchase order, PO line, receipt, customer data table and user loads: save_all() sends full records and answers per record, save_all(full_load=True) is a sync run (records of your source that the run did not send are removed; a rejected record fails the run), patch_all() merges, delete_keys() deletes by key, delete_where() is refused, reads follow a cursor. Rejected records raise RecordsRejected.
  • SupplierLoad(context, source_system='...'): a load writes as one source system, by default the connector's config.source_system.
  • Objects read from the API keep their id, so obj.save() and obj.delete() work after a read. obj.deactivate() sets the active flag to false; purchase orders, PO lines and receipts have none (use delete()).
  • Assigning a child list sends it, also when empty: loc.addresses = [] removes your addresses on a full save. An unset list keeps them.
  • Every POST, PATCH and DELETE carries an Idempotency-Key and is retried with the same key on a connection error, 502, 503, 504 or 409 idempotency_in_progress.
  • InvoiceLoad works with leases: acknowledge, release and integration_result take the InvoiceLease; bulk acknowledge_many, release_many, integration_results; list() follows a cursor.
  • IngestionLoad sends files over 50 MB through presigned uploads and reports duplicates; status() reads a source document.
  • PaymentLine.invoice_token replaces invoice_token_id. PaymentLoad has no list, search or delete.
  • New fields: supplier location is_default; supplier address remit_to_address and status; bank account status; organization timezone; source keys on addresses, bank accounts and organization identifiers. Child ids are strings (UUID).
  • Job scopes as in production: push_invoice may acknowledge and release and post lifecycle messages, get_invoice may upload source documents, get_invoice_status and get_payment may post lifecycle messages, get_purchaseorder and get_receipts may read the master data they map to, get_invoice may read receiving inboxes.
  • Examples renamed after their entrypoint keys: get_supplier.py (was 02_pull_suppliers.py), push_invoice.py (was 05_push_invoices.py), and a new get_custom_1.py.

0.4.8 (2026-10-01)​

  • New entrypoint key get_fx_rates: load the customer's own exchange rates from an ERP. Its job token may read and write exchange rates only.
  • FxRateLoad (lib/objects/fx_rate.py): rates against the base currency or pairs with the base on one side, delete_range() to restate a period, effective() for the rate the reports use and its source. Reference: docs/fx_rate.md.
  • Example entrypoint connectors/get_fx_rates.py (delta cursor, restate option) and fixtures fx_rate.json, fx_settings.json, platform_fx_rate.json; the sandbox simulates the exchange-rate endpoints.

0.4.7 (2026-09-22)​

  • The sandbox enforces token operation scoping like production: a run may only call the operations its entrypoint key allows, anything else gets a 403 from the fake (the same mapping the integration worker and the internal API use).
  • load.find_by_external_id(external_id_1=..., ...): invoices by the external integration ids an integration result stored, in any state.
  • Organization units round-trip: GET /v1/organization returns parent_code next to parent_id, and a save accepts the parent's code or an existing unit's id in parent_id.
  • temp_work/ run folders older than 24 hours are purged at the next run.
  • A handler that returns {'success': False} is reported as failed in production (main.py no longer overwrites it).

0.4.6 (2026-09-22)​

  • load.list() / load.list_all() accept PendingConnectorIntegration: invoices the process engine handed to a connector's push_invoice job and that still wait for its result (status 94). For housekeeping and reconciliation; a push_invoice job gets its own invoice in job_params and never claims from this list. api.get_pending_connector_invoices() is the one-call form.
  • The state name is sent to the API the way the API wants it (lowerCamel), so state='PendingIntegration' works against the real API, not only the fake.
  • The fake API answers the new state (status 94 in the invoice fixture) and accepts both spellings.

0.4.5 (2026-09-13)​

  • Global connectors are templates. Creating a customer connector copies the template's default config, entrypoints, shared modules and code as customer version 1; the copy is edited on the customer from then on. AGENTS.md and README section 7 describe the new setup flow (template, then customer connector from it, or a blank connector).

0.4.4 (2026-09-13)​

  • Shared code modules: a connectors/shared_<name>.py file without run() is a module every entrypoint of the connector can import by file name (from shared_zoho import ZohoBooks). In the admin portal it is pasted on the connector's new "Shared code" tab; the platform writes it next to the entrypoint at run time. The menu lists only files that define run() and names the shared modules.
  • AGENTS.md and README: the one-file rule replaced by the shared module rule; admin portal setup includes the Shared code tab.

0.4.3 (2026-09-12)​

  • SDK and docs: organization units use UUID ids and code is optional when a unit carries a source identity; organization types documented (1 Group, 2 Company, 3 Department); save() sends the whole object.
  • Fake API: organizations and suppliers match soft-deleted rows by source identity and revive them on save, as production does.

0.4.2 (2026-09-12)​

  • AGENTS.md added: one document that gives an AI coding tool or a new developer the full picture (runtime contract, context sources, entrypoint keys, admin portal setup, implementation checklist).

0.4.1 (2026-09-12)​

  • External identity documented: source_system, source_record_id, source_updated_at on suppliers, supplier locations and organization units; 02_pull_suppliers.py (now get_supplier.py) stamps them.
  • docs/organization.md: the read, merge, save pattern for records that people also edit by hand; code optional for units with a source identity.

0.4.0 (2026-09-12)​

  • SDK: external identity fields on Supplier, SupplierLocation, Organization; BaseLoad.index_by_source().
  • Fake API matches on (source_system, source_record_id) before the natural key, as production does.

0.3.0 (2026-09-12)​

  • SDK resynced with the production framework: purchase order, PO line and receipt field names (net_amount, tax_amount, gross_amount, description), discount basis fields on invoices, and three objects that were missing (IngestionLoad, LifecycleMessageLoad, UserLoad).
  • lib/api_client.py ships ApiError, and the sandbox raises it, so except ApiError behaves the same locally and in production.
  • Fixtures use the production invoice field keys (invoiceNumber, grossAmount, matched_supplier_number, ...); a user.json fixture.
  • Fake API: /v1/users, /v1/invoice-import, real source-document result shape.
  • README: settings/ layout and the rules that only bite in production.
  • New docs: ingestion.md, lifecycle_message.md, user.md.

0.2.0 (2026-05-16)​

  • Renamed from ap-receiving-connector-sandbox → nuntiq-connector-toolkit to match the platform rebrand.
  • Bundled with the Nuntiq developer portal.

0.1.0 (2026-04-25)​

  • Initial release. Six example connectors covering the four most common shapes (pull with delta, push with claim/ack/result, lifecycle poll, read-only export).

Stay current​

Toolkit releases are versioned alongside the production framework. Major behavioural changes get a major-version bump; SDK additions are minor. Anything you've written against an older toolkit should run unchanged on the next minor version. Connectors written against 0.2.0 need one check before running on 0.3.0 or later: invoice field keys are the capture template's camelCase names, not snake_case.

The framework itself ships on a separate cadence maintained by Nuntiq. Both share the SDK in lib/ byte-for-byte, so anything that imports cleanly in your toolkit will import cleanly in production at the version you tested against.