Your first connector
Five-minute end-to-end: a connector that lists every organization unit currently in Nuntiq and logs how many it found. Useless in production, but it exercises every piece of the framework: settings, the API client, an entity Load, and logging.
The contract
Every connector file lives in connectors/ and looks like this:
# connectors/get_organization.py
"""One-line summary shown in the menu."""
def run(context):
...
return {'ok': True}
That's the entire contract:
- A top-level
run(context)function. - Returns a dict (or
None; the framework will normalise it).
The filename (without .py) is the entrypoint key: get_supplier,
get_organization, get_purchaseorder, get_receipts, get_invoice,
get_invoice_status, get_payment, get_fx_rates, push_invoice or
get_custom_1..N. The key decides what the run may call. A get_organization
run may read and write organization units; a file with any other name may only
read invoices, in the toolkit as in production. In production you paste the
file into the connector under the same key.
Write it
Create connectors/get_organization.py in your toolkit folder:
"""Hello-world: count the organization units Nuntiq currently knows about."""
from lib.objects.organization import OrganizationLoad
def run(context):
name = context.get_config('connector_name', default='hello')
context.logger.info(f"Hello from {name}!")
load = OrganizationLoad(context)
units = list(load.get_all())
context.logger.info(f"Nuntiq holds {len(units)} organization unit(s).")
for u in units[:5]:
context.logger.info(f" - {u.code}: {u.name}")
return {
'connector_name': name,
'organization_count': len(units),
}
Run it
python nuntiq.py
Pick a settings file, then get_organization from the menu.
2026-10-02 10:23:14 UTC [INFO] Hello from demo_integration!
2026-10-02 10:23:14 UTC [INFO] Nuntiq holds 2 organization unit(s).
2026-10-02 10:23:14 UTC [INFO] - EMEA: EMEA Division
2026-10-02 10:23:14 UTC [INFO] - NA: North America
================ result ================
{
"connector_name": "demo_integration",
"organization_count": 2,
"success": true
}
The numbers come from fixtures/organization.json. Press r at the menu to restore
the seed state after experimenting.
What just happened
python nuntiq.pyloaded the settings file you picked fromsettings/and listed every file underconnectors/that definesrun().- You picked
get_organization. The toolkit importedconnectors/get_organization.pyand calledrun(context)with the permissions of theget_organizationkey. contextis aJobContext, same shape as production. The only difference:context.apiis the fake API client backed byfixtures/*.json.OrganizationLoad(context).get_all()translated toGET /v1/organization?limit=100calls, followingnext_cursoruntil the last page. The fake client read them fromfixtures/organization.json.- Your
returnvalue was echoed back as JSON. The framework adds"success": trueautomatically.
In production, the same code runs unchanged: context.api becomes the real
HTTP client, and OrganizationLoad.get_all() hits Nuntiq's customer API.
Make a small change
Edit fixtures/organization.json and add a third unit. Re-run
get_organization. The count goes up. Press r to reset to the seeded state.
This is the dev loop:
- Edit your
.pyfile (or a fixture). - Re-run
python nuntiq.py→ pick your entrypoint. - See the result.
No restarts, no servers, no waiting.
Failing on purpose
Add a deliberate bug:
context.logger.info(f"Found {1/0} suppliers") # ZeroDivisionError
Run it. You'll see the traceback in stderr and the result JSON will be:
{ "success": false, "error": "division by zero", "error_type": "ZeroDivisionError" }
That's exactly what production does. The connector exits non-zero, the scheduler marks the job failed, the stderr trace lands in the job log.
What's next
- Toolkit overview: what each folder in the zip is for, how the fake API works, what it does and doesn't simulate.
- Concepts → Architecture: how all of this gets packaged and run in production.
- Integration patterns: the four most common connector shapes, with full working code.