Skip to main content

Architecture

A connector running in production is three things layered together:

┌─────────────────────────────────────────────────────────┐
│ Docker container │
│ │
│ /framework/ ← Connector Framework (the SDK) │
│ /connector/ ← Your script bundle │
│ /tmp/job/ ← Per-run inputs & scratch space │
│ │
│ python3 /framework/main.py --entrypoint hello \ │
│ --connector-path /connector \ │
│ --config /tmp/job/config.json │
└─────────────────────────────────────────────────────────┘

Three pieces, deployed independently:

LayerWhat it isUpdated byCadence
Docker base imagePython 3.12 + pre-installed libs (requests, paramiko, authlib, pandas, pdfplumber...)NuntiqPeriodically; when a new library is needed
Frameworkmain.py, lib/, the SDKNuntiqWhen the API contract changes
Connector scriptYour connectors/your_thing.py plus any local helpersYouEvery time you ship a change

The Docker image and framework ship together. Your script is not in the image — it's pulled fresh on every job and cached per version, so a new script version is picked up automatically on the next scheduled run.

What happens when a job runs

  1. The scheduler fires. Nuntiq decides it's time to run entrypoint X for connector instance Y. The job is queued for execution.

  2. A worker picks it up. Nuntiq:

    • Generates a short-lived job token scoped to this run + this customer.
    • Resolves the script bundle version registered against this connector instance.
    • Pulls the instance's secrets and config from the platform.
    • Writes a fresh config.json into a per-job temp directory.
  3. A Docker container launches with three mounts:

    • /framework — the framework, mounted read-only. Your code can't tamper with it.
    • /connector — your script bundle, mounted read-only.
    • /tmp/job — your read-write scratch space for this single run.

    The container's entry command is:

    python3 /framework/main.py \
    --entrypoint hello \
    --connector-path /connector \
    --config /tmp/job/config.json

    The container can reach Nuntiq's internal API (via the context.api client — already wired up for you) and can make arbitrary outbound calls to the third-party systems you're integrating (requests, paramiko, httpx, anything).

  4. The framework boots. main.py parses args, reads config.json, builds the JobContext, captures stdout (so print() from your code can't corrupt the result), and imports /connector/handler.py.

  5. Your run(context) executes. Whatever you do here happens for real: real API calls to Nuntiq, real outbound calls to wherever you point requests/paramiko/httpx, real disk writes under context.work_dir.

  6. The framework collects the result. Whatever your function returned (plus success: true|false) is JSON-encoded and written to stdout as a single line. The worker reads that line; everything else from stderr becomes the job's log file.

  7. Cleanup. The container exits and /tmp/job/ is discarded. Anything you wrote to context.work_dir is gone.

Inside the container

PathWhat's there
/framework/The framework code (read-only).
/connector/Your script bundle for the version registered against this connector instance (read-only).
/tmp/job/Per-run scratch space (read-write). Includes config.json and is the root of context.work_dir.

The two API surfaces your code talks to

There are two HTTP surfaces a connector touches:

  • Nuntiq's API — what context.api.get('/v1/supplier') hits. The framework wires up authentication for you using the run's job token; no API keys or credentials to manage. Only reachable from inside the connector container.
  • The external system you're integrating — whatever third party you requests.get, paramiko.SFTPClient.put, or httpx.post to. Use credentials from context.secrets.

The framework's API client and the public, customer-facing Nuntiq REST API share endpoint shapes for convenience, but the connector path uses a short-lived job token, not a long-lived API key — no key needs to be issued or rotated for your connector to work.

Job inputs (config.json)

Everything your connector sees comes from this single JSON file written into the container at runtime:

/tmp/job/config.json
{
"job_token": "eyJhbGciOiJI...",
"log_token": "log-9f3a2e1b...",
"customer_number": "10000001",
"api_base_url": "https://internal-api/...",

"secrets": {
"sftp_password": "...",
"api_token": "..."
},
"config": {
"connector_name": "coupa",
"sftp_host": "files.acme.com",
"ext_reference_value": "COUPA"
},
"job_params": {
"lookback_days": 7,
"page_limit": 500
}
}

You don't read this file directly — context.get_secret(...), context.get_config(...), and context.get_param(...) are the accessors. See JobContext for the full picture.

What's next

  • JobContext — the context argument in detail.
  • Logging — what shows up in the job log, and how.
  • Delta state — incremental sync without writing watermark-tracking boilerplate.