JobContext
context is the only argument your run function gets. It's the doorway to
everything the framework provides.
def run(context):
context.api # HTTP client to the Nuntiq customer API
context.logger # structured logging
context.secrets # dict of secret values (passwords, tokens)
context.config # dict of non-secret settings
context.job_params # dict of per-run parameters
context.customer_number # tenant identifier
context.work_dir # per-run scratch directory (path, str)
The three input bags
The Nuntiq admin portal lets the operator configure three separate
dictionaries per connector instance. They land on context exactly as
configured.
context.secrets — encrypted at rest
Use for: passwords, API tokens, OAuth client secrets, private keys.
Stored encrypted at rest in the platform's secret store. Never logged by the framework, never displayed back in the admin UI after the operator saves them.
api_token = context.get_secret('api_token')
if not api_token:
raise RuntimeError("secrets.api_token is required")
sftp_password = context.get_secret('sftp_password')
Use context.get_secret(key, default=None) rather than context.secrets[key]
when you want a graceful fallback.
context.config — non-secret settings
Use for: URLs, hostnames, supplier codes, feature flags, anything that's sensitive enough to be customer-specific but not sensitive enough to encrypt.
api_url = context.get_config('api_url') # required
connector_name = context.get_config('connector_name') # required
include_zeros = context.get_config('include_zero_amount', default=False)
These survive across runs. They're set once at registration time and don't change unless the operator edits the connector instance.
context.job_params — per-run parameters
Use for: backfill ranges, page limits, feature toggles you want the scheduler to override.
since_override = context.get_param('since_override') # e.g. backfill from a date
limit = int(context.get_param('page_limit', default=100))
Set by the scheduler when it triggers a job. Different scheduled runs of
the same entrypoint can pass different job_params — that's how you'd, say,
run a connector with lookback_days=7 nightly but lookback_days=30 weekly
for the same entrypoint.
context.api — the Nuntiq API
The authenticated HTTP client. Every call carries the job's short-lived token
and the current customer_number header.
Two ways to use it:
# 1. Low-level — you know exactly which endpoint you want
suppliers = context.api.get('/v1/supplier', params={'page': 1, 'limit': 100})
# 2. Typed entity Loads — recommended for everything CRUD-shaped
from lib.objects.supplier import SupplierLoad
load = SupplierLoad(context)
for s in load.get_all():
print(s.supplier_number)
The entity Loads (SupplierLoad, InvoiceLoad, OrganizationLoad, etc.) are
thin typed wrappers over the same HTTP calls. See BaseLoad for
the patterns common to all of them.
context.logger — what shows up in the job log
context.logger.info("Starting pull")
context.logger.warn("API returned 0 rows", detail={'cursor': cursor})
context.logger.error("SFTP login failed", step='sftp_connect')
Output goes to stderr (which the worker captures as the job log file) and optionally to a structured log endpoint. See Logging for the detail.
context.customer_number — tenant scope
A string. Set by the worker when it constructs the job. Every API call carries it as a header automatically — you almost never read it directly. It's there when you need to include it in an external system call:
external_id = f"{context.customer_number}-{invoice.invoice_token}"
context.work_dir — scratch space
A per-run directory you can write to. Returns an absolute path as a string.
import os, csv
path = os.path.join(context.work_dir, 'export.csv')
with open(path, 'w') as f:
csv.writer(f).writerows(rows)
context.logger.info(f"Wrote export to {path}")
Created lazily on first access. In production it's wiped when the container
exits. In the toolkit it persists under <repo>/temp_work/ so you can inspect
what your entrypoint produced. See Work dir for what to use it
for.
Lifecycle
- The framework constructs
contextexactly once, before importing yourhandler.py. - It's passed to
run(context). - When
runreturns, the framework flushes any buffered log entries and exits.
You should treat context as a value, not something you can mutate. It's safe
to pass to helper functions, store on classes, whatever — the framework
doesn't peek inside it after you receive it.