Skip to main content
Team and Enterprise A service account is the identity a script, a scheduler, a CI job or an agent acts as in a workspace. It has a name, a description, an owner, the account permissions it holds and its own project access. Its tokens prove it. A token can be added, replaced or revoked without changing who the job is, so project access and history stay with the service account, and the service account stays with the workspace when a person leaves. An owner or admin manages service accounts on Team settings, then Service accounts. Service accounts are created in the hosted dashboard on Team and Enterprise. A Community deployment has no dashboard and no supported way to create one. Integrations and public interfaces shows what a new token looks like and how to use it.

Who can do what

Giving a service account access to a project is a project admin’s act, in one of two places. On that project’s Access page the service account is a Service account row beside people and model Agents. Choose it from Person or Agent, pick an access level up to Operator and choose Add to project. The picker offers every active service account that holds no access on that project yet, whether or not it has a token. A disabled or retired service account is not offered. Its existing row stays, marked Disabled or Retired, with its level frozen (Operator. Enable the service account to change this. or Operator. Retired, so it can only be removed.) and Remove still available. Allowing a service account to delete Dataset Versions in the project still needs a live token that reaches it. A project admin can also list, grant, change or remove a service account’s access to that project through the API’s project-service-account routes or the Python SDK’s service_accounts.project_access, under a personal token that holds admin on the project. Nitsor does not issue personal tokens today, so these calls cannot succeed yet. See Manage from the API and the Python SDK.

What a service account holds

Nothing is ever deleted. A retired service account stays in the list, and History and Dataset Versions keep naming it.

Create a service account

Choose New service account on Team settings, then Service accounts. The form has four steps, and nothing is created until the last one. A template only preselects the next step. Nothing about it is stored. The API and the Python SDK have no templates: a script passes the permission list itself. If you turn the first token off, the service account exists with its permissions and cannot authenticate until you add a token. The finish screen then says the service account is ready and holds its permissions and no token yet, and What next ends with Add a token when you are ready. A project admin can give it project access on a project’s Access page before it has a token. The access works as soon as a token exists. Creating a service account grants it no project access. It sees no project until a project admin gives it access on the project’s Access page (project Settings, then Access), at Operator at most. When it is created with a first token, the page shows that token once, covered, with Copy token, Show token and the Use it snippets, and a Service account created. message confirms it. Nitsor keeps only a hash of the token, so reloading the page cannot show it again. A create that was retried after a lost response does not create a second service account, and the page says the first token’s secret cannot be shown. Add a token from the service account’s page instead. The What next card links to each project’s Access page when the workspace has one to three projects, and to the account home otherwise.

The list

Team settings, then Service accounts, lists them newest first, 25 to a page. The filter above the table shows All, Active, Disabled or Retired, with counts when the workspace has 200 service accounts or fewer. On a narrow screen each service account is a card: its name and state, the description, the same permissions count and one line with its tokens, any expiry warning and its last use. With no service accounts the page explains what one is and offers New service account to an owner or admin.

A service account’s page

Choose a name to open its page. It has six sections. A saved edit shows a message: Name and description saved. or Permissions saved.

Tokens

A service account holds up to 10 live tokens, so a job can move without an outage: add a token for the new deployment, switch over, then revoke the old one. At 10, Add token is switched off and says so. Roll stays available at 10, because it ends the token it replaces. The token list shows these columns. The list names the replacement chain: a token that was rolled says what replaced it, and the new one says what it replaced. The page reads the 200 most recent tokens of any state and says so when older ones may not be listed.

Add a token

Choose Add token, at the top of the service account’s page or in the Tokens section. Add token is not offered while the service account is disabled or retired. The same options appear as the third step of the create form. A token can do at most what its service account can. The new token is shown once, covered, like any new token, and an added message confirms it. A token’s own list and its project limit never change after it is made. To change them, make a new token.

Expiry

Every token expires, 90 days after creation unless you choose another time, and never more than 365 days out. Shorten on a live token moves its expiry earlier, never later, to a preset that falls before the current expiry or a custom day. The token keeps working until the new cutoff, and the leases it already issued stop working within one hour after it: they refuse new reads. A download already under way can finish. To extend a token, roll it. Shorten is offered while the service account is disabled. The API’s shorten takes any instant in the future that is not later than the current expiry. The one-hour floor applies to the expiry of a new token, not to a shorten.

Roll a token

Roll replaces a token with a successor. Choose an Overlap from no overlap to 24 hours in whole hours (24 hours is selected) and confirm with Roll token. The successor has the same label and the same reach, a 90-day expiry, and is shown once. The replaced token keeps working until the earlier of its own expiry and the end of the overlap, then stops: a token that expires in ten minutes stops in ten minutes whatever overlap you choose. Revoke it early once your deployments use the new token. After a roll the old and the new token share a name until the old one stops, so name the old token by its id when you revoke it through the API. Roll is offered on a live token that has no successor and only while the service account is active. A successor cannot be rolled until the token it replaced has stopped, and Roll is switched off and says when that wait ends. A token that already has a successor is not rolled again: roll the newer one. A token narrowed to a project can be rolled only while the service account still holds a grant on that project, because a roll never restores a removed grant.

Revoke a token

Revoke opens a confirmation with an optional reason of up to 500 characters. Anything using the token is refused from then on. Leases it already issued stop working within one hour, and a download already under way can finish; data a job has already downloaded stays with it. Revoking is permanent: a revoked token stays in the list for audit, with who revoked it and why, and cannot be restored. Revoke is offered while the service account is disabled.

Permissions

The service account holds a list of account permissions. Every token inherits it unless the token was made narrower, and a token’s own list is an upper bound. What a request can do is what the service account holds and the token presented both allow, and every request uses only the token it presented. Project access is separate: account permissions give no access to any project. Wherever you pick permissions, a wide page sets each read beside the write it pairs with and a narrow page stacks them, so you see at once whether a token may only look or may also change. A service account can never hold members:write, billing:write, clearance:write or service-accounts:manage. The forms show them switched off with the reason, so they cannot be ticked. The API refuses a list that names one and creates or changes nothing: as token_scope_escalation when the caller’s token does not hold the permission, which is checked first, and as service_account_permission_ineligible when it does. A token’s own list of permissions (a first token’s, or one chosen in Add token) is checked the other way round: eligibility first, so a forbidden permission there is refused as service_account_permission_ineligible even when the caller’s token does not hold it, then the service account’s list, then the caller’s reach. You can only give a service account permissions you hold yourself; when you edit the list, only the permissions you add must be ones you hold. Edit permissions on the Permissions section changes the list. The dashboard needs at least one permission; the API accepts an empty list, a service account that reaches only what its project grants give it. Before you save, the page says what the change does.
  • Adding a permission reaches every inheriting token at its next request.
  • Removing a permission takes effect for new requests at once. Leases already issued that relied on it stop working within one hour: their further reads are refused, and a download already under way can finish.
Editing permissions is allowed while the service account is disabled, so the list is right when you enable it, and refused once it is retired. A person holds Manage service accounts (service-accounts:manage) to manage service accounts through the API and the Python SDK. Owners and admins hold it through their role. A personal token carries it only when its person holds it and the token lists it. A service account can never hold it, so a service account cannot manage service accounts.

Disable, enable and retire

Danger zone holds the two stops. Disable is a pause you can undo. Retire is permanent. Nothing is deleted by either. Disable takes an optional reason of up to 500 characters, kept in the audit record. Enable and Retire take none in the dashboard, and Retire asks you to type the service account’s name exactly as shown. The API accepts a reason for all three. A disabled or retired service account shows a notice naming who did it, when and why, and Disabled or Retired appears in the list. Disabling a service account that is already disabled changes nothing. A retired service account is refused every change except revoking a token, enable included. An agent or script that presents a token of a retired service account gets 401 AUTH_INVALID_TOKEN with the reason service_account_retired. A token of a disabled service account gets service_account_disabled when the token is otherwise live; one that is also revoked or expired keeps that reason first, because service_account_disabled is given only when an enable would bring the token back. So an agent can tell a pause from an ending. When a token is refused says what to do with each. The service account’s page shows an owner or admin its state.

Owner

Every service account has one owner, an active member of the workspace in any role. The owner is the person to ask about it: they do not give it its access, and changing the owner changes no permission, token or project access.
  • The default owner is the person who creates it. Choose another member in step 1 of the create form.
  • Through the API and the Python SDK, a member is named by a membership id or by the email they sign in with. The server matches an email against the workspace’s active members without regard to case, so the member need not own a service account yet. An email asks the roster who holds an address, so it needs a token that also holds members:read, which owners and admins hold; without it the server refuses with owner_email_needs_members_read and reads no member. A membership id needs no more than service-accounts:manage. An email no active member has is refused as owner_not_member, whether it was never a member’s or its member left. An email that two members share, or on a roster too large to search whole (over 2,000 memberships, past members included, where even a single match is unresolved), is refused as owner_email_unresolved, and then the membership id names the member. A refusal changes nothing.
  • Change owner on the Overview opens the list of active members. A long roster adds Search members by email.
  • When the owner leaves the workspace, the service account keeps running. Nothing is paused and no token changes. Owners and admins see one row on Home, under Needs you: “2 service accounts need a new owner.” Its Open Service accounts link opens the list, and Which service accounts under it names each one: “Choose a new owner for nightly-export. Its owner left the account; it keeps running.” A disabled service account stays disabled, and choosing an owner does not enable it. Choose owner opens the member list. A retired service account raises no row. If the row can be counted but its service accounts cannot be named, Home says “Which service accounts need a new owner could not be loaded.” with a retry control. When more service accounts need an owner than the list shows, a last line says so, and the rest appear as these are cleared.
  • The list shows (left the account) beside an owner who has left, until someone names a new one.

Manage from the API and the Python SDK

The API and the Python SDK do what the dashboard does with service accounts and their tokens, under the same server rules. They need a personal token that holds Manage service accounts, except the project_access calls, which need a personal token that holds admin on the project and no account permission. Nitsor does not issue personal tokens today, so these calls cannot succeed. Manage service accounts in the dashboard. A service account’s own token never manages a service account: every one of these routes refuses it with AUTHORIZATION_DENIED (403) naming service-accounts:manage. The Python SDK is not on PyPI. These forms are what its tests use.
The three calls that mint a secret, create with a first token, tokens.create and tokens.replace, take an idempotency key inside the request model. A retry under the same key answers replayed without the secret, because the secret was shown once on the first answer. If you lost that answer, replace the token. A refusal carries a reason that names the rule, such as token_live_limit_reached or service_account_name_taken. project_access.grant and project_access.remove also take an idempotency key, and a retry under it answers the first call’s outcome and changes nothing. The async client, AsyncNitsor, mirrors every method above under await, including await client.service_accounts.project_access.grant(...).

Agents as callers

An agent that holds a token is a first-class caller. It can ask who it is and when its token ends before it acts, you can stop it in one step, and every event it writes carries its service account’s identity.

Who am I

GET /api/v1/session answers who the token acts as, which token it is, what it can do and where. The session fields are in the API reference. The answer describes only the token that made the request, never another token of the same service account. An agent can read it once at the start of a run:
Reading the answer first replaces learning the limits from refusals. The reach is paged: pass limit and cursor and keep following nextCursor until it is null, because a page can hold fewer projects than limit, or none, while nextCursor is set. Fields that a server older than them leaves out are absent from the answer.

Expiry warnings

In the last 14 days of a token’s life, every authenticated response carries a Nitsor-Token-Expires header with the instant the token stops working. The Python SDK turns it into one warning: it issues one NitsorTokenExpiryWarning, a UserWarning, per process per token, the first time it sees the header. Python’s warning filters and -W flags apply to it. The warning names the whole days left, rounded down, and where to replace the token, and never the token. It names the token by its last four characters. Pass the token through NITSOR_TOKEN or a stored profile and not inline in source: Python prints the source line of a call in the traceback of an uncaught exception, so a token written on that line is printed with it. An agent that must not run past its token’s end can compare session.token.expiresAt with the clock, or turn the Python warning into an error with warnings.simplefilter("error", NitsorTokenExpiryWarning), importing the class with from nitsor import NitsorTokenExpiryWarning. An owner or admin replaces the token before then with Roll, which keeps the old token working for the overlap while the deployment switches.

When an agent is stopped

Disable is the kill switch for one agent. Its next request to the API fails with 401 AUTH_INVALID_TOKEN and, for an otherwise live token, the reason service_account_disabled, a job stops at its next step, and leases it already holds stop working within one hour, though a download already under way can finish. After Enable, nothing restarts on its own. See Disable, enable and retire.

When a token is refused

A refused token gets 401 AUTH_INVALID_TOKEN, and the Python SDK does not retry it. When Nitsor knows the token, the answer carries a reason and the server’s own words. The SDK exposes them on the error as reason, remediation and, for an expired token, expired_at. What an agent does depends on the reason. The API reference lists the wire fields. An agent that reads GET /api/v1/session at the start of a run learns when its token ends before it is refused: token.expiresAt is the instant, and the expiry warning repeats it in the last 14 days. After a refusal that call is refused too, so for an expired token take the time from expiredAt.

Next step

Read the Public HTTP API for the route contract, or Integrations and public interfaces to see what a new token looks like and how to use it.
PlansTeam and Enterprise
Last verified2026-10-21