Workspaces

Storage connections

client.storageConnections manages the storage connections a new workspace can keep its files in, instead of Nace's managed storage. See Use your own storage for the concept, and the API reference for every field.

  • A storage connection is your own S3 bucket or Azure Blob container. A workspace created with one keeps its files there, under its own folder, and never falls back to Nace's managed storage.
  • Who can do what: any member of your account can list, read and use a connection. Only its creator, or an owner or admin, can change it; anyone else gets PermissionDeniedError, code access_denied. A connection's can_manage says whether your key can.
  • Cost: managing storage connections costs nothing and works at any balance.
  • Availability: where a deployment doesn't offer storage connections, every method rejects with NotFoundError, code storage_connection_unavailable.

A connection's status is one of:

StateWhat you can do
"draft"Set it up, save credentials and validate it. It can't hold a workspace yet.
"ready"Give it to a new workspace. Validated: Nace can write, read and delete in the destination.
"degraded"A later check failed. Fix the access in your cloud and validate it again.
"revoked"Nace stopped using it, and workspaces in it stop reading and writing. Validate one you revoked with reactivate: true to restore it.

A connection goes through these steps before a workspace can use it:

  1. Create a draft with the bucket or container (storageConnections.create).
  2. Read what to create in your cloud: an IAM role for S3, a service principal's access for Azure (storageConnections.setup). Optionally save your AWS account and role name, or your Azure subscription and resource group, first, so the setup is filled in (storageConnections.setSetup).
  3. Create the role or service principal in your cloud, then save it (storageConnections.setCredentials).
  4. Validate it and wait for the job (storageConnections.validate, storageConnections.waitForValidation). A new IAM role or Azure role assignment can take a few minutes to apply, so a fresh one may fail at first: validate again.
  5. Create a workspace with the connection's id as storage_connection_id (workspaces.create).

Only a connection whose status is "ready", with credentials_pending, validation_running and reactivation_requires_approval all false, can be given to a new workspace.

Every method that works on one connection takes its connectionId first; one that isn't your account's rejects with NotFoundError, code storage_connection_unavailable.

  • Errors: a change by someone who isn't the connection's creator, or an owner or admin, rejects with PermissionDeniedError (access_denied). Credentials the cloud refuses reject with UnprocessableEntityError (storage_credentials_refused). delete on a connection a workspace uses, and validate on one Nace revoked, reject with ConflictError. A connectionId that is empty or holds /, ? or #, or a limit outside 1 to 200, throws a NaceError before any request.
  • Retries: create carries an Idempotency-Key, minted per call when idempotency_key is omitted, so a retried create returns the same connection. validate and delete aren't retried unless you pass retry in options: a retry after the first call went through starts a second validation, or rejects with NotFoundError for the deleted connection. Every other method is retried.
  • Secrets: an Azure client_secret is sent once and never returned. Debug logs show it as ***, and a refusal never quotes it.

List storage connections

client.storageConnections.list(params?, options?) returns APIPromise<StorageConnectionPage>, one page of your account's storage connections. params is a StorageConnectionListParams.

ParameterTypeRequiredDescription
limitnumberNo1 to 200. Default: 50.
cursorstring | nullNoThe previous page's next_cursor. Keep going until it's null.
list-storage-connections.ts
const page = await client.storageConnections.list({ limit: 20 });
for (const connection of page.items) {
  console.log(connection.id, connection.name, connection.status);
}

Iterate over storage connections

client.storageConnections.iter(options?) returns AsyncGenerator<StorageConnection>, following every page, 200 a page.

iter-storage-connections.ts
for await (const connection of client.storageConnections.iter()) {
  console.log(connection.id, connection.destination.type);
}

Create a storage connection

client.storageConnections.create(params, options?) returns APIPromise<StorageConnection>: a draft. params is a CreateStorageConnectionParams.

ParameterTypeRequiredDescription
namestringYesThe name people see: 1 to 200 characters.
destinationStorageDestinationYesAn S3Destination or an AzureBlobDestination. It can't be changed later.
idempotency_keystringNoSent as the Idempotency-Key header, up to 200 characters: the same key returns the same connection. Minted per call when omitted.
S3Destination fieldTypeDescription
type"s3"An S3 bucket.
bucketstringYour S3 bucket.
regionstringThe AWS region the bucket is in, such as us-east-1.
prefixstringA base prefix in the bucket, without a leading or trailing slash. Each workspace gets its own folder beneath it. Default: "".
AzureBlobDestination fieldTypeDescription
type"azure_blob"An Azure Blob container.
accountstringThe Azure storage account name.
containerstringYour Blob container.
prefixstringA base prefix in the container, without a leading or trailing slash. Each workspace gets its own folder beneath it. Default: "".
create-storage-connection.ts
const connection = await client.storageConnections.create({
  name: "Audit evidence",
  destination: { type: "s3", bucket: "acme-audit-evidence", region: "us-east-1", prefix: "nace" },
});
console.log(connection.id, connection.external_id);

Read a storage connection

client.storageConnections.get(connectionId, options?) returns APIPromise<StorageConnection>.

ParameterTypeRequiredDescription
connectionIdstringYesThe connection.
get-storage-connection.ts
const connection = await client.storageConnections.get(connectionId);
console.log(connection.status, connection.validated_at);

Read the setup

client.storageConnections.setup(connectionId, options?) returns APIPromise<StorageConnectionSetup>. It says what to create in your cloud so Nace can reach the destination, generated from the setup options you saved.

ParameterTypeRequiredDescription
connectionIdstringYesThe connection.

For S3 it has an IAM role trust_policy carrying the connection's external_id, its permissions_policy, a cloudformation_template that creates the role, the role_arn that role will have, and a launch_url that opens the stack in the AWS console once your AWS account id and role name are saved. For Azure it has azure_cli, the az commands that give your service principal access to the container.

storage-setup.ts
const setup = await client.storageConnections.setup(connectionId);
console.log(setup.trust_policy, setup.permissions_policy);

Save the setup options

client.storageConnections.setSetup(connectionId, setup, options?) returns APIPromise<StorageConnectionSetup>, generated from what you saved. These are the non-secret inputs only.

ParameterTypeRequiredDescription
connectionIdstringYesThe connection.
setupStorageSetupOptionsYesAn S3SetupOptions or an AzureSetupOptions. Its type must match the connection's destination.
S3SetupOptions fieldTypeDescription
type"s3"For an S3 destination.
access_method"auto" | "manual""auto" for the CloudFormation stack, "manual" to write the role yourself. Default: "auto".
aws_account_idstring | nullThe AWS account the role is created in: 12 digits.
role_namestring | nullThe name of the role the stack creates.
kms_key_arnstring | nullA KMS key the role may use; the bucket's settings decide encryption.
AzureSetupOptions fieldTypeDescription
type"azure_blob"For an Azure Blob destination.
subscription_idstring | nullThe subscription the storage account is in.
resource_groupstring | nullThe resource group the storage account is in.
tenant_idstring | nullYour Microsoft Entra tenant.
client_idstring | nullThe application (client) id to give access to.
set-storage-setup.ts
const setup = await client.storageConnections.setSetup(connectionId, {
  type: "s3",
  aws_account_id: "444455556666",
  role_name: "nace-workspaces",
});
console.log(setup.launch_url, setup.role_arn);

Save the credentials

client.storageConnections.setCredentials(connectionId, credential, options?) returns APIPromise<StorageConnection>, with credentials_pending until a validation passes. The credentials are write-only: they are never returned.

ParameterTypeRequiredDescription
connectionIdstringYesThe connection.
credentialStorageCredentialYesAn AwsRoleCredential for S3, or an AzureServicePrincipalCredential for Azure.
AwsRoleCredential fieldTypeDescription
type"aws_role"An IAM role.
role_arnstringYour IAM role that trusts Nace's roles with the connection's external_id.
AzureServicePrincipalCredential fieldTypeDescription
type"azure_service_principal"A Microsoft Entra application.
tenant_idstringYour Microsoft Entra tenant.
client_idstringThe application (client) id, with Storage Blob Data Contributor on the container.
client_secretstringThe application's secret. Never returned.

Credentials the service refuses reject with UnprocessableEntityError, code storage_credentials_refused.

set-storage-credentials.ts
await client.storageConnections.setCredentials(connectionId, {
  type: "azure_service_principal",
  tenant_id: process.env.AZURE_TENANT_ID ?? "",
  client_id: process.env.AZURE_CLIENT_ID ?? "",
  client_secret: process.env.AZURE_CLIENT_SECRET ?? "",
});

Validate a storage connection

client.storageConnections.validate(connectionId, params?, options?) returns APIPromise<DocumentJob>. The job, of kind storage_validation, checks Nace can write, read and delete in the destination. Once it succeeds, the connection is ready. params is a ValidateStorageConnectionParams.

ParameterTypeRequiredDescription
connectionIdstringYesThe connection.
reactivatebooleanNoRestore a connection you revoked once the validation passes. Default: false.

A connection that can't be validated now, for example because Nace revoked it, rejects with ConflictError.

client.storageConnections.validation(connectionId, options?) returns APIPromise<DocumentJob>: the connection's latest validation job. While it is queued or running, the connection has validation_running. A failed job's error says what Nace couldn't do.

ParameterTypeRequiredDescription
connectionIdstringYesThe connection.

Wait for the validation

client.storageConnections.waitForValidation(connectionId, options?) returns Promise<DocumentJob>. It reads the latest validation job with storageConnections.validation until its status is one of TERMINAL_STATUSES, and returns it. A job that ends failed is returned, not thrown: read its error.

ParameterTypeRequiredDescription
connectionIdstringYesThe connection.

options is a WaitForValidationOptions: a RequestOptions plus pollIntervalMs and timeoutMs. Each read takes the request options. pollIntervalMs is the milliseconds between reads, default 2000. timeoutMs is how long to wait for the job to settle, in milliseconds: the whole wait, not one read, default 180000 (3 minutes).

When the time runs out it throws JobTimeoutError, whose job is the job as last read and timeoutMs the wait budget. The job keeps going: wait again later. Aborting signal throws APIUserAbortError.

ConstantValueDescription
DEFAULT_STORAGE_VALIDATION_POLL_MS2000The default pollIntervalMs of storageConnections.waitForValidation.
DEFAULT_STORAGE_VALIDATION_TIMEOUT_MS180000The default timeoutMs of storageConnections.waitForValidation.
validate-storage-connection.ts
await client.storageConnections.validate(connectionId);
const job = await client.storageConnections.waitForValidation(connectionId);
console.log(job.status, job.error);

Revoke a storage connection

client.storageConnections.revoke(connectionId, options?) returns APIPromise<StorageConnection>, with status "revoked". Nace stops using it: workspaces in it stop reading and writing until it is validated again with reactivate: true. Their files stay in your bucket.

ParameterTypeRequiredDescription
connectionIdstringYesThe connection.
revoke-storage-connection.ts
const revoked = await client.storageConnections.revoke(connectionId);
console.log(revoked.status, revoked.revoked_reason);

Delete a storage connection

client.storageConnections.delete(connectionId, options?) returns Promise<void>. Nothing in the bucket is touched. A connection a workspace uses rejects with ConflictError. It isn't retried unless you pass retry in options.

ParameterTypeRequiredDescription
connectionIdstringYesThe connection.
delete-storage-connection.ts
await client.storageConnections.delete(connectionId);

Create a workspace in your storage

Pass a ready connection's id as storage_connection_id. See Create a workspace.

workspace-with-storage.ts
const connection = await client.storageConnections.get(connectionId);
if (connection.status === "ready" && !connection.credentials_pending && !connection.validation_running) {
  const workspace = await client.workspaces.create({ name: "fy25-audit", storage_connection_id: connection.id });
  console.log(workspace.storage?.location);
}

On this page