Skip to main content
All plans These steps work on Team and Enterprise. You need a workspace and an agent token. A Community deployment has no supported way to issue the token; tokens are created in the hosted app on Team and Enterprise. The Python SDK is not on PyPI. Register a source with POST /api/v1/sources, the call behind client.sources.register. Registering a source tells Nitsor which objects in your storage make up one piece of volumetric data. Nitsor reads the headers, hashes what it can, and keeps a reference; registration does not upload a copy of the scan. A read-only connection stores no scan pixels or pixel-derived files in Nitsor storage. Data boundary describes what crosses and what does not.

What this guide covers

Registration happens through the API, and the Python SDK wraps the call. The package is not on PyPI, so the Python snippets below show the calls the SDK makes, and the HTTP route behind each one is what runs. The JSON file the registration snippet reads is the request body of the API call behind it. The dashboard’s Data tab lists what has been registered and turns a Source into work; it does not register. Eight steps, in order. Steps 1 to 6 register the source. Steps 7 and 8 turn it into work. The last section covers the case where registration succeeds and the source still does not appear.

Before you start

You need five things.
  • A workspace on app.nitsor.com. A Community deployment has no supported way to issue the agent token this guide uses; tokens are created in the hosted app on Team and Enterprise.
  • The owner or admin role on it, or someone who holds it and can act for you. Creating a service account needs the owner or admin role, which holds service-accounts:manage. Connecting storage needs storage:write, and registering a Source needs sources:register. The owner and admin roles hold both. A member’s role holds neither, so a member can do these steps only after an owner or admin gives them that one capability on top of the role, in Team settings, Members.
  • The API origin of your deployment. Hosted production uses https://api.nitsor.com, which is also the SDK’s default. Community uses the deployment’s own site URL, NITSOR_CONVEX_SITE_URL in its .env. NITSOR_API_URL takes this origin.
  • A bucket Nitsor can read and the object keys inside it. An owner or admin connects it through the dashboard or personal authority and submits its keys securely there. Nitsor encrypts the keys and does not return them in responses, events, logs or errors. Do not send keys through the contact form.
  • Python 3.11 to 3.13 for the Python snippets.

Step 1. Create a service account and its token

The API authenticates a request with an agent token, the token of a service account. Your dashboard sign-in is not one, and no browser session or account identifier works in its place. Storage key setup uses an owner or admin’s dashboard session or personal authority. An owner or admin opens Team settings, then Service accounts, and chooses New service account. They name it, tick the permissions this guide needs on the Permissions step, keep Create a token now on and choose Create service account. The token is shown once, covered. Copy token puts it on the clipboard without showing it, and Show token prints it on screen. Only its SHA-256 is stored. The service account’s page lists it with its status, and it expires 90 days after you create it unless you choose another time, at most 365 days. Treat it like any other secret: never paste it into logs, tickets, chat, or shared shell history. Revocation is an event with attribution. A new service account sees no projects. A project admin gives it access on the project’s Access page (project Settings, then Access), at Operator at most. client.projects.list() shows only the projects where the service account holds read or more, and client.assets.attach in step 8 needs operate. Service accounts and tokens covers the rest of the page, including how to roll or revoke a token. A token cannot create another one. A service account can never hold members:write, billing:write, clearance:write or service-accounts:manage. The form shows them switched off, so they cannot be ticked, and the server refuses any request that names one and creates nothing.

Step 2. Put the API address in your terminal

Use your deployment’s API origin. This address is not a secret. The assignment has no export, so the value stays in this one shell and no child process inherits it. Run every remaining step in that same shell.

Step 3. Enter the agent token in your terminal

Paste the token at the prompt. The prompt does not echo it, and the shell does not record it in history. This assignment has no export either.

Step 4. Check the token

Save this as check_session.py:
Run it with the two variables handed to that one command:
The prefix hands the token to that one command without exporting it. Nitsor() reads NITSOR_TOKEN and NITSOR_API_URL, so the token is not written in the script or passed to Python as an argument. session.get() calls GET /api/v1/session, which names who the token acts as, so a mistyped, expired or revoked token fails here, before any registration. Run every later snippet the same way.

Step 5. Register a storage connection

An owner or admin opens Team settings, then Storage connections, and connects the customer bucket. They submit keys in the dashboard or through personal authority. Nitsor seals them with encryption and never returns key values in responses, events, logs or errors. Agent credentials cannot add or change keys or enable writing. A new customer connection is read-only. Nitsor keeps Source metadata, small labels, reviews, audit events and Dataset Version manifests, but no scan pixels or pixel-derived files. Masks, stored viewer representations, uploads, model outputs and Dataset Version files carrying masks need write access. An owner or admin enables writing after the connection passes its checks. Write access does not turn on uploads. An owner or admin must separately turn on Accept uploads. Enabling this setting requires passing the test write, create-only, multipart and browser-access checks. Uploads then go to <write prefix>/nitsor-uploads/ in the connection’s write destination. A read-only connection cannot accept uploads. Hosted uploads default to Nitsor storage; the owner can turn that destination off. For an existing account-owned credential handle, an agent credential with storage:write can register a connection using POST /api/v1/storage-connections. This route binds an existing handle; it does not add keys or enable writing. The handle binds the endpoint, bucket, region, addressing mode and prefix scope to a read or read-write role. GET /api/v1/storage-credential-handles lists handles without returning key values. Operator-provisioned handles remain available for deployment-managed connections. The following fields describe that existing-handle request: The request body is strict. Sending the retired credentialEnvironment field is refused with STORAGE_CREDENTIAL_ENVIRONMENT_FORBIDDEN. Keep the connection id from the response; step 6 sends it as storageConnectionId. GET /api/v1/storage-connections/{id} requires storage:read and reads one connection; an id belonging to another account answers 404. Its credential is { kind: "handle", handleId, label } or { kind: "legacy-unbound" }. The answer includes previewObjectBytes, the bytes of viewer representations Nitsor wrote into that bucket. If your account has no handle for the storage location, an owner or admin connects it through the storage settings page before continuing. A connection registered before handles reads back as legacy-unbound and reads nothing until it is rebound there. The health check tells you whether a read succeeds.

Step 6. Register the source

Write one JSON file holding exactly the request body the API declares for POST /api/v1/sources, then load it and register it:
SourceRegisterRequest.model_validate checks the file against the route’s schema before anything is sent, and the API refuses a request body over 1 MiB. A successful registration answers 201 with the Source, its sourceId (content identity), its sourceVersionId (this registration), sourceFormat, viewability, geometry for DICOM, a per-object manifest and any issues found. A status of partial_failure means some objects registered and others were rejected, each with an issue code. Print the answer whole; the sourceVersionId is what a Project Asset in step 8 binds. For ultrasound, register each geometry-free image separately. Uncompressed single-frame 2D ultrasound DICOM uses PixelSpacing or suitable Ultrasound Regions for calibration. An uncalibrated image can be viewed but has no physical scale. NumberOfFrames must be 1; multi-frame cine and native 3D/4D ultrasound are not supported. NIfTI and NRRD are not supported. A successful registration does not establish compatibility with every hosted model.

Step 7. Bind a taxonomy version

A project with no taxonomy bound cannot create a task, because the task would have no categories to annotate with. Open the project’s Guidance tab, then its Taxonomy page, and save a definition; every save binds a new version whose hash covers the schema. Anyone holding operate on the project can do this, and so can an account owner or admin.

Step 8. Create the first task

Open the project’s Data tab. Registration happened in your script, so refresh the page. The source appears with its format, file count and identity, and offers Details, View, Open in editor and, for a viewable source, Metadata. Optionally declare a Project Asset first, a label plus one Source Version per series role, such as primary. An asset is what a Dataset Version later proves membership against. client.assets.attach does the same from a script and writes a commit:
Pick the source and create the task. Creating the task is what attaches the source to the project, and the task enters the queue when the bound pipeline routes new work to a person.

If the source does not appear

client.sources.register can succeed for a source the Data tab still will not offer. Start here:
This needs sources:browse. It shows whether the source exists, its format, and whether it is viewable. Read the result against these four causes. If client.assets.attach answers SOURCE_VERSION_AMBIGUOUS, the same content has been registered more than once and a binding by sourceId cannot choose between the versions. Bind the explicit sourceVersionId.

Next step

Read Public HTTP API for the route contract and the full issue-code vocabulary.
PlansAll plans
Last verified2026-10-21