iSCSI Connector API Reference
This page lists every phase the iSCSI connector accepts and every config field each phase reads, so you can drive the whole clone cycle over REST instead of the vME console.
It assumes you already know the shape of an agent job. If you have not read it yet, start with Agent Jobs, which covers the request envelope, job states, polling, timeouts, and error handling for all connectors. Installing and enrolling the agent is covered in the connector guide.
Every job in this reference is created the same way:
POST /agent-job
{
"agent_id" : "agt-abc123",
"phase" : "<one of the phases below>",
"config" : { },
"secrets" : { },
"workflow_id" : "wf-abc123",
"wizard_step" : "source_prep",
"role" : "source"
}
The last three are optional and only matter when the job belongs to an onboarding workflow. workflow_id is what lets a phase's results - such as resolvedSqlInstance - persist for later jobs in the sequence. wizard_step records the job as that step's outcome, so omit it for a job that should not count as one, such as a backup-set probe. role is source or clone, and is also accepted as a filter on GET /agent-job.
Phases
| Phase | Runs on | What it does |
|---|---|---|
DiscoverHost | Either host | Inventories the host - local vs iSCSI disks, SQL instances and databases, volumes and drive letters in use, portals, targets, and sessions, plus the host's vME folder, its host facts, and the connector's capabilities. Read-only; touches no storage. |
ConnectTargets | Either host | Connects the selected iSCSI targets so their disks become visible to later phases. Does not format or mount. |
SourcePrep | Source | Prepares the source volume (initialize, format NTFS 64K, mount at its folder under the host's vME folder) and then migrates databases onto it if a migrate block is present. A source prepared at drive letters keeps them. |
SourcePrepOnly | Source | The prepare half of SourcePrep, with no migration. |
SourceMigrateOnly | Source | The migrate half of SourcePrep, against LUNs that are already prepared. |
CloneMount | Clone | Connects the cloned targets, brings only those targets' disks online without formatting, mounts the volume at its folder under the host's vME folder, and attaches the database files. A clone of a source prepared at drive letters can also be given drive letters. |
CloneUnmount | Clone | Detaches the workflow's databases, then disconnects its iSCSI sessions and offlines only the disks behind them. A volume mounted under the vME folder, source or clone, has its mount point released and its folder removed. Can also forget named targets and portals. |
Quiesce | Either host | Before a bookmark or rollback of a volume this host has attached: sets every database with files on that volume OFFLINE, flushes the volume and takes its disk offline. Leaves the iSCSI session and the mount point in place. Refuses a database that also has files on another volume. |
Resume | Either host | After the bookmark or rollback: brings the disk back online, waits for the volume to appear at the same folder (and re-adds the mount point if it does not), and sets the databases Quiesce took offline back ONLINE. |
vME runs Quiesce and Resume itself around a snapshot or rollback of an attached volume; you do not normally queue them. Snapshot and clone creation are not agent phases. They are done in vME through the source and clone APIs, between SourceMigrateOnly and CloneMount.
Typical sequence
Source host vME Clone host
----------- --- ----------
DiscoverHost
ConnectTargets
SourcePrepOnly
SourceMigrateOnly
snapshot source
create clone
DiscoverHost
CloneMount
... use the databases ...
CloneUnmount
SourcePrep collapses the two source phases into one job. Use the split phases when you want to prepare LUNs well ahead of the migration, or retry a failed migration without re-preparing.
Quiesce and Resume sit outside this sequence. vME queues them itself around a bookmark or a rollback of a volume that a host has attached - see Quiesce and Resume.
Shared config fields
These sit at the top level of config, alongside the migrate, attach, and unmount blocks rather than inside them.
| Field | Type | Description |
|---|---|---|
portal | String | The vME appliance's iSCSI portal host or IP. Validated and probed on port 3260 before the initiator is touched, so a bad value fails fast. |
targets | Array or String | The targets this job acts on, as whole IQNs: an array of strings, a comma-separated string, or an array of objects that carry the IQN as iqn beside that target's CHAP credentials (see CHAP). Matched outright, never as a fragment, so a job cannot select another workflow's target. Preferred over manualIQN. The console sends the IQNs vME recorded when it created the exports. |
manualIQN | String | IQN filter - only matching targets are considered. A whole IQN is matched outright. A fragment is matched against whole IQN name components, so a dataset-path fragment such as tank.sources.sales covers both LUNs of a source created before vME 1.10 (...sales.data and ...sales.log). A comma-separated list is accepted. Targets exported by vME 1.10 or later are named by the dataset's guid alone, so scope those with targets or a whole IQN. |
dataset | String | Restricts targets to those belonging to a named vME dataset. An alternative to manualIQN. |
mountRoot | String | The host's vME folder, a folder on a local NTFS volume, for example C:\vME. With mountId, the volume is mounted at a folder under it and no drive letter is used: a source at <mountRoot>\sources\<mountId>, a clone at <mountRoot>\clones\<mountId>, each holding MSSQL\Data, MSSQL\Logs and MSSQL\Backups. The drive letter keys are then not needed. On teardown the same pair scopes the detach to that one volume and removes its folder afterwards. The host reports its own setting in DiscoverHost under defaults.vme_root; a job names the folder it wants. |
mountId | String | The volume's folder name under sources or clones. The console sends the dataset's guid. Letters, digits, dot, dash and underscore only. Both keys sit at the top level of config for every phase that places, removes, or pauses a volume: SourcePrep, SourcePrepOnly, SourceMigrateOnly, CloneMount, CloneUnmount, Quiesce and Resume. Send both or neither. |
targetSelection | String | Which of the discovered targets to connect, as a comma-separated list of indexes, or ALL. Optional when manualIQN or dataset is set. |
clearOldPortals | Boolean | Removes previously configured portals before connecting. Read by the prep phases only. Default false. |
job_timeout_seconds | Number | Overrides the job timeout. See Timeouts. |
CHAP
Supply these in secrets, not config, so they are stored encrypted and kept out of logs and job listings.
| Field | Type | Description |
|---|---|---|
chapUser | String | CHAP username for one-way CHAP. |
chapPassword | String | CHAP secret. Windows enforces 12-16 ASCII characters. |
mutualChapPassword | String | Secret for mutual CHAP. Omit for one-way CHAP. |
targets | Array | Per-target credentials, for when different targets use different secrets. Each entry carries the whole IQN it belongs to as iqn. An entry without a chapUser only names a target and leaves the top-level credentials in force. The same list also scopes the job, see Shared config fields; when targets is given in both secrets and config, the secrets list is the one used. |
The Windows iSCSI Initiator rejects secrets outside 12-16 ASCII characters at connection time. Prefer 16 characters of mixed-case alphanumeric - Windows may auto-decode a pure-hex string to fewer bytes and then reject it as too short.
A target's IQN is matched against each targets entry as whole IQN name components. A target with no matching entry falls back to the top-level chapUser / chapPassword, and connects without authentication when neither is set. mutualChapPassword is always global: Windows supports a single initiator reverse CHAP secret system-wide.
A worked secrets example with per-target credentials:
{
"chapUser": "default-user",
"chapPassword": "default-password",
"mutualChapPassword": "global-mutual-secret",
"targets": [
{
"iqn": "iqn.2025-01.com.storage:lun-data",
"chapUser": "user-for-data",
"chapPassword": "secret-for-data"
},
{
"iqn": "iqn.2025-01.com.storage:lun-log",
"chapUser": "user-for-log",
"chapPassword": "secret-for-log"
}
]
}
The targets array is optional: without it, the top-level credentials apply to every connected target.
DiscoverHost
Requires no config. Send {} and read the inventory back off the completed job - supplying portal refreshes that portal first, so newly created targets appear.
{ "agent_id": "agt-abc123", "phase": "DiscoverHost", "config": {} }
Run this first on any host you are about to act on. Its output gives you the disk numbers, target indexes, and database IDs that the later phases need. Its defaults block also carries vme_root, the folder this host mounts sources and clones under, empty when the host has none set.
The result also carries two blocks that describe the host and the connector:
| Block | What it holds |
|---|---|
host_capabilities.capabilities | The names of what this connector can do. See Capabilities. |
host_facts | Facts about the host. See Host facts. |
Capabilities
The connector names what it can do. The same list is sent when the agent enrolls, with every heartbeat, and in every DiscoverHost result. A name that is missing from the list means the connector on that host cannot do that thing, so check for the name rather than for a version.
| Name | Meaning |
|---|---|
folder_mount | Sources and clones mount as folders under the host's vME folder, from mountRoot and mountId. |
quiesce_resume | The Quiesce and Resume phases. |
forget_named_target | CloneUnmount forgets only the targets named in unmount.removeTargets. |
forget_named_portal | CloneUnmount forgets only the portals named in unmount.removePortals. |
attach_as | CloneMount attaches the clone's one database under the name in attach.attachAs. See Attaching under a chosen name. |
Host facts
The agent collects these when its service starts and every 15 minutes after that, and sends the latest set with each heartbeat. A DiscoverHost job collects a fresh set and returns it as host_facts. On GET /agent, both the capabilities and the host facts appear under the agent's attributes.
| Field | Meaning |
|---|---|
os.caption, os.version, os.build, os.architecture | Windows edition, version, build, and architecture. |
cpu_logical, memory_gb | Logical processors and physical memory. |
domain, part_of_domain | The host's domain or workgroup, and whether it is joined to a domain. |
ip_addresses | The host's IPv4 addresses, without loopback and link-local addresses. |
initiator_iqn | The host's own iSCSI initiator name. |
vme_root | The host's vME folder. Empty when none is set. |
sql_instances | One entry per SQL Server instance: name, service_name, status, database_count, product_name (for example SQL Server 2022), product_version, major_version, product_level, update_level, edition, collation, is_clustered, is_hadr_enabled. The version fields are absent for an instance that could not be reached, such as a stopped one. |
warnings, collected_at | What could not be read, and when the facts were collected (UTC). |
ConnectTargets
Reads portal, clearOldPortals, and targets - the IQNs to connect, as an array or a comma-separated string.
Unlike the prep and clone phases, this one does not honour manualIQN or dataset. If targets is omitted it connects every target discovered on the portal, so name them explicitly unless that is what you want.
{
"agent_id": "agt-abc123",
"phase": "ConnectTargets",
"config": {
"portal": "10.0.0.10",
"targets": [
"iqn.2024-01.com.example:tank.sources.sales.data",
"iqn.2024-01.com.example:tank.sources.sales.log"
]
}
}
targets here is a plain listFor ConnectTargets, targets is only the list of IQNs to connect, as strings. In the other phases the same key scopes the job and can also carry per-target CHAP credentials as objects - see Shared config fields and CHAP.
SourcePrepOnly and SourcePrep
Prepares the source LUNs. SourcePrep additionally runs the migration when a migrate block is present.
Required: portal, and mountRoot and mountId at the top level of config. The volume is mounted at <mountRoot>\sources\<mountId>, labelled SQL_DB, and takes no drive letter.
Also required unless manualIQN or dataset is set: dataDiskNumber, logDiskNumber, targetSelection. With manualIQN or dataset present the connector finds the disks from the job's targets, so these can be omitted. Send the target's whole IQN in manualIQN as well as in targets.
Only the disks on the job's selected targets are prepared. The phase waits up to diskWaitSeconds for a disk to appear on each of them. If none of the selected targets has a disk by then, the job fails and lists the iSCSI disks it did find; no other disk is partitioned or formatted in its place.
Runs created with drive letters keep them: a source on a data LUN and a log LUN is prepared at dataDriveLetter and logDriveLetter, with no mountRoot or mountId. A folder holds one volume, so a source on two LUNs always uses drive letters.
| Field | Type | Description |
|---|---|---|
diskWaitSeconds | Number | Seconds to wait for a disk to appear on each selected target. Default 60. |
dataDiskNumber | Number | Windows disk number of the data LUN. |
logDiskNumber | Number | Windows disk number of the log LUN. |
confirmPrep | Boolean | Default true. Setting false aborts before any destructive step - useful as a dry run. |
dataDriveLetter | String | Drive-letter layout only. Drive letter for the data LUN, for example E. Labelled SQL_Data. |
logDriveLetter | String | Drive-letter layout only. Drive letter for the log LUN, for example F. Labelled SQL_Log. |
forceDriveLetters | Boolean | Drive-letter layout only. Reassigns the requested letters even if they are in use elsewhere. Default false. |
SourcePrep and SourcePrepOnly initialize and format the selected LUNs as NTFS with 64K allocation units. Only new, empty LUNs should be selected.
{
"agent_id": "agt-src01",
"phase": "SourcePrepOnly",
"config": {
"portal": "10.0.0.10",
"manualIQN": "iqn.2026-09.com.enov8:g-10057799540195812700",
"targets": ["iqn.2026-09.com.enov8:g-10057799540195812700"],
"mountRoot": "C:\\vME",
"mountId": "10057799540195812700"
}
}
SourceMigrateOnly and the migrate block
Moves databases onto the prepared LUNs. SourceMigrateOnly requires a migrate block; SourcePrep accepts one optionally.
Required: the same mountRoot and mountId at the top level of config that the prepare phase was given. The databases are placed in MSSQL\Data, MSSQL\Logs and MSSQL\Backups under the source's folder. For a source prepared at drive letters, name those letters inside migrate as dataDriveLetter and logDriveLetter. A probeOnly run needs neither.
Also required: dbIds, unless one of dbNameFilter, backupPaths, or sourcePaths.dataPath is supplied - those select the databases a different way.
| Field | Type | Description |
|---|---|---|
dataDriveLetter | String | Drive-letter layout only. Drive letter holding \MSSQL\Data and \MSSQL\Backups. |
logDriveLetter | String | Drive-letter layout only. Drive letter holding \MSSQL\Logs. |
dbIds | String | Comma-separated database IDs to migrate, as reported by DiscoverHost. |
dbNameFilter | String | Selects databases by name pattern instead of by ID. |
dbPrefix | String | Prefix for the new database names. Defaults to ISCSI_. Letters, digits, and underscores only. |
sqlInstance | String | Pins the SQL instance to use. When omitted, the connector auto-detects: the default instance if present, otherwise the first named instance. |
selectionInstance | String | The SQL instance the database selection was made against, normally the instance DiscoverHost reported the dbIds from. If the instance the job resolves to is a different one, the phase fails before any database is backed up, restored or copied, because database IDs differ between instances. Different spellings of the same instance, such as localhost, . and the computer name, count as the same. Leave it out to skip the check. |
backupPaths | Array | Existing .bak files or folders to restore from, instead of taking a fresh backup. Accepts directories, wildcards and individual files, mixed freely. Files are grouped into sets by the BackupSetGUID in their header. |
probeOnly | Boolean | Reports the backup sets found in backupPaths and exits without restoring. Requires backupPaths, needs no folder or drive letters, and can run before the LUNs are prepared. See Results. |
sourcePaths | Object | { "dataPath": "...", "logPath": "..." } for raw .mdf/.ldf file copy. Either a directory or a bare drive letter, which is scanned recursively. |
confirmStart | Boolean | Default true. false aborts before the migration starts. |
confirmSelection | Boolean | Default true. false aborts after the database selection is resolved but before work begins. |
execute | Boolean | Default true. false generates the SQL and reports what it would do without running it. |
Migration modes
The mode is chosen by which selection fields are present:
| Mode | Set | Behaviour |
|---|---|---|
| Backup and restore | dbIds or dbNameFilter | Takes a fresh backup of each selected database and restores it onto the LUNs under the new name. |
| Restore from existing backups | backupPaths | Restores from .bak files already on disk, with no BACKUP DATABASE step. |
| Raw file copy | sourcePaths | Offlines the database and copies its .mdf/.ldf files onto the LUNs. Setting this ignores backupPaths. |
Worked bodies for each mode are in Bodies for each migration mode.
Files in backupPaths are grouped into sets by the BackupSetGUID in their header, so a full and a differential for the same database are recognised as separate sets rather than one striped set. The newest full is restored and the newest differential that chains to it is applied on top. Stripes belonging to one set may be spread across several of the supplied paths, and an incomplete set stops the run with the files it found named.
To see what a path holds before restoring, run the job with migrate.probeOnly and read backupSets from the result, then pass the files you want back as migrate.backupPaths.
Results
On success the phase reports resolvedSqlInstance - the instance it actually used. When the job belongs to a workflow, vME persists this onto the workflow so the teardown job can target the same instance. Read it from GET /agent-job.
sqlInstance is probed before it is used. If the pinned instance cannot be reached, the connector falls back to auto-detection rather than failing, so a typo can result in the databases landing on a different instance. Check resolvedSqlInstance on the completed job to confirm where the work went.
A probeOnly run reports backupSets instead of restoring anything. This is response data - read it from the completed job's result with GET /agent-job, not something you send.
Each database found carries its name, whether it can be restored, and the sets the restore would use:
"backupSets": {
"databases": [
{
"name": "Sales",
"restorable": true,
"reason": "",
"full": {
"guid": "A1B2C3D4-...",
"finishDate": "2026-04-01 22:14:07",
"stripes": ["D:\\Backups\\sales_full_stripe1.bak", "D:\\Backups\\sales_full_stripe2.bak"]
},
"diff": {
"guid": "E5F6A7B8-...",
"finishDate": "2026-04-02 22:11:53",
"stripes": ["D:\\Backups\\sales_diff.bak"]
}
}
]
}
| Field | Meaning |
|---|---|
restorable | Whether this database can be restored from what the paths hold. |
reason | Empty when restorable is true. On a failure it names the problem - an incomplete set with the missing stripes listed, or a differential with no matching full. |
full | The full set the restore would use. null when restorable is false. |
diff | The differential that chains to that full, or null when there isn't one. |
stripes | The file paths belonging to that set. Pass these back as backupPaths to restore this database, including every stripe. |
Unlike resolvedSqlInstance, backupSets is not persisted onto the workflow - read it from the job that produced it.
Migrating on already-prepared LUNs, selecting by database ID:
{
"agent_id": "agt-src01",
"phase": "SourceMigrateOnly",
"config": {
"job_timeout_seconds": 86400,
"mountRoot": "C:\\vME",
"mountId": "10057799540195812700",
"migrate": {
"dbIds": "7,8",
"dbPrefix": "ISCSI_"
}
}
}
Omit sqlInstance to let the connector auto-detect, or set it to pin the instance. Bodies for the other two modes are in Bodies for each migration mode.
CloneMount and the attach block
Required at the top level: portal, attach. Plus targetSelection, unless manualIQN or dataset is set - with a filter present the connector defaults to all matching targets.
Required inside attach: sqlInstance, dbSelection.
Also required at the top level: mountRoot and mountId. The clone is mounted at <mountRoot>\clones\<mountId> and takes no drive letter. A clone on a data LUN and a log LUN is mounted at <mountRoot>\clones\<mountId>\data and <mountRoot>\clones\<mountId>\log. Runs created with drive letters keep them: such a clone is given dataDriveLetter and logDriveLetter inside attach, with no mountRoot or mountId.
Only the disks on the job's selected targets are brought online. The phase waits up to attach.diskWaitSeconds for a disk to appear on each selected target, then waits up to attach.diskOnlineTimeoutSeconds for each of those disks to come online. If none of the selected targets has a disk, the job fails and lists the iSCSI disks attached to the host; no other disk is used in its place. Any other offline iSCSI disk on the host is left offline. A clone carries the same disk signature as its source, so bringing the wrong one online would make Windows rewrite that signature.
Before bringing each of the job's own disks online, the connector clears its read-only flag, so that Windows can give a clone mounted on its source's host a new disk ID. Any other disk with a clashing ID is left offline.
The connector turns Windows automount off while it mounts a clone and turns it back on when the job ends, whether the job succeeds or fails. If a job is stopped before it can do so, the next clone mount on that host turns automount back on when it ends.
| Field | Type | Description |
|---|---|---|
sqlInstance | String | The SQL instance to attach the databases to. |
dbSelection | String | Which discovered databases to attach - a comma-separated list, or ALL. |
dbPrefix | String | Prefix applied to the attached database names. Send an empty string to attach the names exactly as they are on the clone. |
attachAs | String | Attaches the clone's one database under this name instead of its own. Letters, digits and underscores only, 128 characters at most. Leave it out, or send an empty string, to attach under the database's own name. See Attaching under a chosen name. |
autoPair | Boolean | Default true. Pairs each .mdf with its .ldf automatically. Set false to supply pairs explicitly. |
databases | Array | Explicit file pairs, used when autoPair is false. |
diskWaitSeconds | Number | Seconds to wait for a disk to appear on each selected target. Default 60. |
diskOnlineTimeoutSeconds | Number | Seconds to wait for each of the job's disks to come online. Default 600. A very large disk can take minutes to come online. A disk still offline at the end fails the job, naming the disk and the reason. |
dataDriveLetter | String | Drive-letter layout only. Drive letter to assign to the cloned data volume. |
logDriveLetter | String | Drive-letter layout only. Drive letter to assign to the cloned log volume. A clone of a single-volume source has no separate log volume; send the data letter again. |
skipPartitionAssign | Boolean | Drive-letter layout only. Assigns dataDriveLetter and logDriveLetter to the clone's volumes by their role, without stopping to ask about each partition. Default false. |
The prefix is applied only when the name in the database's primary data file does not already start with it, so it is never doubled. A restored database already carries the migration's prefix and is left alone; a raw-copied file keeps its original name and gets the prefix. Sending the same prefix the ingest used is therefore the normal setting.
Send an empty string or NONE to attach names exactly as they appear on the clone. Whichever you choose, make sure the teardown job's unmount.dbPrefix matches what the databases ended up being called.
The attached name is read from each database's primary data file, not from the file name on disk. A renamed .mdf still attaches under the name it was created with.
Without attachAs, a database whose name already exists on the SQL instance is skipped: the job log shows a warning and that database is not attached. This is what happens when a clone is mounted on its source's own SQL Server, where the source database still holds the name. To attach such a clone, give it a new name with attachAs.
Mounted at its folder under the host's vME folder:
{
"agent_id": "agt-clone01",
"phase": "CloneMount",
"config": {
"portal": "10.0.1.10",
"manualIQN": "iqn.2026-09.com.enov8:g-10057799540195812700",
"targets": ["iqn.2026-09.com.enov8:g-10057799540195812700"],
"mountRoot": "C:\\vME",
"mountId": "10057799540195812700",
"attach": {
"sqlInstance": "localhost",
"dbSelection": "ALL",
"dbPrefix": "ISCSI_"
}
}
}
targetSelection can be left out here because manualIQN is set; the connector then uses every target that matches.
A clone of a source prepared at drive letters, given drive letters:
{
"agent_id": "agt-clone01",
"phase": "CloneMount",
"config": {
"portal": "10.0.1.10",
"manualIQN": "tank.clones.sales",
"attach": {
"sqlInstance": "localhost",
"dataDriveLetter": "G",
"logDriveLetter": "H",
"skipPartitionAssign": true,
"dbSelection": "ALL",
"dbPrefix": "ISCSI_"
}
}
}
Attaching under a chosen name
attach.attachAs attaches the clone's database under a name you choose, for example to put a clone on the same SQL Server as its source. The connector reports that it can do this with the attach_as capability.
The name must meet all of these rules:
- The clone holds exactly one database.
- The name starts with
attach.dbPrefix, compared without regard to case. WhendbPrefixis blank orNONE, any name is allowed. - No database of that name already exists on the instance, compared without regard to case.
- Letters, digits and underscores only, 128 characters at most.
A name with other characters, or longer than 128 characters, fails the job before it starts. If any other rule is not met, the phase fails before any database is attached, and the job log says which rule. A name in use is never skipped: unlike a database attached under its own name, it fails the phase.
Send the teardown job an unmount.dbPrefix that the new name starts with, normally the same prefix, so CloneUnmount finds the database under its new name.
A clone mounted on its source's host, attached under a new name:
{
"agent_id": "agt-src01",
"phase": "CloneMount",
"config": {
"portal": "10.0.0.10",
"manualIQN": "iqn.2026-09.com.enov8:g-4185529034771265112",
"targets": ["iqn.2026-09.com.enov8:g-4185529034771265112"],
"mountRoot": "C:\\vME",
"mountId": "4185529034771265112",
"attach": {
"sqlInstance": "localhost",
"dbSelection": "ALL",
"dbPrefix": "ISCSI_",
"attachAs": "ISCSI_Sales_Test"
}
}
}
CloneUnmount and the unmount block
Required at the top level: unmount.
Required inside unmount: dbSelection, disconnectTargets.
Also required: manualIQN at the top level, if either of those two is set to MATCH - MATCH scopes the work by IQN, and without it the job would silently do nothing, so the job is rejected. Send the whole IQNs in targets as well. For a volume mounted under the vME folder, source or clone, send the same mountRoot and mountId the mount was given: the detach is then scoped to that volume's folder, its mount point is released, and the folder is removed once the disks are gone. Runs created with drive letters keep them: without mountRoot and mountId the detach is scoped by the volume's drive letters.
Before a database is detached, any other session connected to it is closed and its open work rolled back, since SQL Server refuses to detach a database that is in use. A database left attached is returned to multi-user access.
| Field | Type | Description |
|---|---|---|
dbSelection | String | Which databases to detach - a comma-separated list, ALL, MATCH to scope by manualIQN, or SKIP / NONE to detach nothing. |
disconnectTargets | String | Which iSCSI sessions to disconnect - a comma-separated list, ALL, MATCH, or SKIP / NONE. |
dbPrefix | String | Only detach databases with this prefix. Defaults to ISCSI_. Must match the names the databases were actually attached under, or nothing is detached. Letters, digits, and underscores only - it is used as a literal prefix, so SKIP and NONE are treated as prefixes here, not as sentinels. |
sqlInstance | String | The instance to detach from. Optional - when omitted the connector auto-detects, and for workflow jobs vME supplies the instance recorded by the migration. |
fullCleanup | Boolean | Default true. After the scoped teardown, performs a full iSCSI reset: disconnects every session on the host, not just this workflow's, removes every persistent target and portal, and clears the initiator's list of known targets. Send false for a scoped teardown - vME does on every teardown it queues. |
removeTargets | Array or String | Whole IQNs, as an array or a comma-separated string. The host forgets these targets - each one's session is disconnected and its persistent registration removed - and nothing else. Use it for a target whose dataset no longer exists. |
removePortals | Array or String | Portal addresses, as an array or a comma-separated string. The host forgets these portals and nothing else. Use it for an appliance that is no longer in use. |
fullCleanup: falseTwo separate settings can take other iSCSI workloads offline on a shared server:
disconnectTargets: "ALL"disconnects every session on the host. PreferMATCHwithmanualIQN, or an explicit list.fullCleanupdefaults totrue, and does the same thing regardless of whatdisconnectTargetsis set to. Omitting it opts in.
vME sends fullCleanup: false on every teardown it queues, from the console or on its own. A hand-written request that leaves it out does not.
Setting dbSelection to SKIP while disconnecting the targets leaves the databases registered in SQL Server with their files gone, so they show as RECOVERY_PENDING. The same happens if dbPrefix does not match the attached names, because nothing gets detached. Confirm the databases detached before disconnecting the LUNs.
{
"agent_id": "agt-clone01",
"phase": "CloneUnmount",
"config": {
"manualIQN": "iqn.2026-09.com.enov8:g-10057799540195812700",
"targets": ["iqn.2026-09.com.enov8:g-10057799540195812700"],
"mountRoot": "C:\\vME",
"mountId": "10057799540195812700",
"unmount": {
"dbSelection": "MATCH",
"disconnectTargets": "MATCH",
"dbPrefix": "ISCSI_",
"fullCleanup": false
}
}
}
Forget a named target or portal
A host keeps a target after its dataset is destroyed, and a portal after its appliance is gone, until something removes them. removeTargets and removePortals remove only the entries they name. Name the same targets in manualIQN with dbSelection and disconnectTargets set to MATCH, so any database or session still on them is released first:
{
"agent_id": "agt-clone01",
"phase": "CloneUnmount",
"config": {
"manualIQN": "iqn.2026-09.com.enov8:g-10057799540195812700",
"unmount": {
"dbSelection": "MATCH",
"disconnectTargets": "MATCH",
"fullCleanup": false,
"removeTargets": ["iqn.2026-09.com.enov8:g-10057799540195812700"]
}
}
}
To forget only a portal, set dbSelection and disconnectTargets to NONE and name the address in removePortals. The job log reports each named entry as forgotten, still listed by the host, or not known to this host.
Quiesce and Resume
These two phases pause a volume that a host has attached, so vME can take a bookmark of it or roll it back without the host holding stale data. vME queues them itself: Quiesce immediately before the bookmark or rollback, and Resume straight after it, whether or not the storage operation succeeded. You do not normally create these jobs by hand.
Quiescefinds every database with files on the volume and sets each online oneOFFLINE WITH ROLLBACK IMMEDIATE, confirming the state it reaches. It then flushes the volume and takes the disk offline. The iSCSI session stays logged in and the folder mount point stays in place. If a database also has files on other storage, the phase refuses and takes nothing offline, because changing this volume alone would leave that database inconsistent.Resumebrings the disk back online, clears its read-only flag, and waits for the volume to come back at its folder, adding the mount point again if it does not. It then sets the databasesQuiescetook offline back online. A database that was already offline before the quiesce is left offline.
Required at the top level: targets or manualIQN, naming the target the volume is exported as. Both phases find the disk by its target, never by disk number, since a disk can come back with a different number. mountRoot and mountId are optional, but must be sent together; with them, Quiesce refuses to act unless the disks in scope are mounted under that volume's folder.
quiesce block (optional):
| Field | Type | Description |
|---|---|---|
sqlInstance | String | The SQL instance to check. Detected automatically when omitted. |
databases | Array | Database names. Narrows what is taken offline to this list. Without it, every database with files on the volume is taken offline. |
allowSpanningDatabases | Boolean | Takes a database offline even when it also has files on other storage. Default false, which refuses instead. |
flushVolumeCache | Boolean | Flushes the volume before the disk goes offline. Default true. |
requireSql | Boolean | Fails the phase if SQL Server cannot be reached. Default false: an unreachable SQL Server cannot be holding the volume open, so the phase goes straight to the disk. |
stateTimeoutSeconds | Number | Seconds to wait for each database to reach OFFLINE. Default 60. |
resume block (optional):
| Field | Type | Description |
|---|---|---|
sqlInstance | String | The SQL instance to bring the databases back online on. |
databases | Array | The databases to bring back online, as names or as { "name": ..., "wasOnline": ... } objects. Normally the databases the Quiesce job reported; an entry with wasOnline: false is left alone. Every entry needs a name. Without this list, every database with files on the volume that is offline or recovery-pending is brought online. |
accessPaths | Array | Where the volume should come back, normally the accessPaths of the disks the Quiesce job reported. |
waitSeconds | Number | Seconds to wait for the volume to come back at its access paths. Default 60. |
repairMount | Boolean | Adds the folder mount point again if the volume does not come back there on its own. Default true. |
stateTimeoutSeconds | Number | Seconds to wait for each database to reach ONLINE. Default 600, since recovery on the volume can take a while. |
Both phases report what they did on the completed job's result, including when they fail:
| Phase | Result fields |
|---|---|
Quiesce | sqlInstance, sqlAvailable; databases, one { name, wasOnline, state, spansOtherVolumes } per database found on the volume; databasesFailed, one { name, reason } per database that would not go offline; disks, one { number, targetIqn, accessPaths, isOffline } per disk; mountFolder; flushed; and error when the phase failed. |
Resume | sqlInstance, sqlAvailable; databasesOnline, the names brought back or found already online; databasesFailed, one { name, reason } per database that did not come back; disksOnline; accessPaths; repairedMount; waitedSeconds; and error when the phase failed. |
A Resume body built from a Quiesce result:
{
"agent_id": "agt-clone01",
"phase": "Resume",
"config": {
"targets": ["iqn.2026-09.com.enov8:g-10057799540195812700"],
"manualIQN": "iqn.2026-09.com.enov8:g-10057799540195812700",
"mountRoot": "C:\\vME",
"mountId": "10057799540195812700",
"resume": {
"sqlInstance": "localhost",
"databases": [{ "name": "ISCSI_Sales", "wasOnline": true }],
"accessPaths": ["C:\\vME\\clones\\10057799540195812700\\"]
}
}
}
Timeouts
The connector's defaults, on top of the general timeout rules:
| Phase | Default timeout |
|---|---|
SourcePrep | 86400 seconds (24 hours) |
SourceMigrateOnly | 86400 seconds (24 hours) |
Quiesce | 900 seconds (15 minutes) |
Resume | 3600 seconds (1 hour) |
| All other phases | 3600 seconds (1 hour) |
The migration phases default high because a backup and restore of a large database routinely runs for hours. Quiesce defaults low because taking databases offline is short, bounded work; one still running after 15 minutes is stuck rather than slow. If you are restoring a very large database, or restoring across a slow link, raise job_timeout_seconds above the default rather than relying on it.
timed_out before its timeoutSeparately from job_timeout_seconds, vME marks a job timed_out when the agent stops reporting for longer than the staleness window - a service restart or a network blip is enough, even though the work on the host continues.
That state is not terminal: a returning agent can still move the job to completed or failed. A client polling for a terminal state must keep polling through it, and raising job_timeout_seconds does not prevent it.
Config field to environment variable
For seven of the nine phases the agent runs the same PowerShell scripts you would run by hand, passing config through as environment variables. This mapping is useful when you are reading a script, comparing a failing API job against a working manual run, or translating an existing config.json into API calls.
DiscoverHost and ConnectTargets are the exceptions: the agent implements them directly and never invokes the scripts. That is why ConnectTargets takes an explicit targets list rather than honouring manualIQN or dataset - the IQN filtering lives in the PowerShell, which those two phases do not run.
| Config field | Environment variable | Script |
|---|---|---|
portal | ISCSI_PORTAL | USECASE1, USECASE3 |
manualIQN | ISCSI_MANUAL_IQN | USECASE1, USECASE3, UNMOUNT, QUIESCE, RESUME |
dataset | ISCSI_EXPECTED_DATASET | USECASE1, USECASE3, QUIESCE, RESUME |
targetSelection | ISCSI_TARGET_SELECTION | USECASE1, USECASE3 |
clearOldPortals | ISCSI_CLEAR_OLD_PORTALS | USECASE1 |
diskWaitSeconds | ISCSI_DISK_WAIT_SECONDS | USECASE1 |
dataDiskNumber | ISCSI_DATA_DISK | USECASE1 |
logDiskNumber | ISCSI_LOG_DISK | USECASE1 |
dataDriveLetter | ISCSI_DATA_LETTER | USECASE1 |
logDriveLetter | ISCSI_LOG_LETTER | USECASE1 |
confirmPrep | ISCSI_CONFIRM_PREP | USECASE1 |
forceDriveLetters | ISCSI_FORCE_DRIVE_LETTERS | USECASE1 |
chapUser | ISCSI_CHAP_USER | USECASE1, USECASE3 |
chapPassword | ISCSI_CHAP_SECRET | USECASE1, USECASE3 |
mutualChapPassword | ISCSI_MUTUAL_CHAP_SECRET | USECASE1, USECASE3 |
targets | ISCSI_TARGET_IQNS | USECASE1, USECASE3, UNMOUNT, QUIESCE, RESUME |
targets (as JSON, for per-target CHAP) | ISCSI_CHAP_TARGETS | USECASE1, USECASE3 |
mountRoot | ISCSI_MOUNT_ROOT | USECASE1, USECASE2, USECASE3, UNMOUNT, QUIESCE, RESUME |
mountId | ISCSI_MOUNT_ID | USECASE1, USECASE2, USECASE3, UNMOUNT, QUIESCE, RESUME |
migrate.dataDriveLetter | ISCSI_MIGRATE_DATA_DRIVE | USECASE2 |
migrate.logDriveLetter | ISCSI_MIGRATE_LOG_DRIVE | USECASE2 |
migrate.dbIds | ISCSI_MIGRATE_DB_IDS | USECASE2 |
migrate.dbNameFilter | ISCSI_MIGRATE_DB_NAME_FILTER | USECASE2 |
migrate.dbPrefix | ISCSI_MIGRATE_DB_PREFIX | USECASE2 |
migrate.sqlInstance | ISCSI_SQL_INSTANCE | USECASE2 |
migrate.selectionInstance | ISCSI_MIGRATE_SELECTION_INSTANCE | USECASE2 |
migrate.backupPaths | ISCSI_MIGRATE_BACKUP_PATHS | USECASE2 |
migrate.probeOnly | ISCSI_MIGRATE_PROBE_ONLY | USECASE2 |
migrate.sourcePaths.dataPath | ISCSI_MIGRATE_SOURCE_DATA_PATH | USECASE2 |
migrate.sourcePaths.logPath | ISCSI_MIGRATE_SOURCE_LOG_PATH | USECASE2 |
migrate.confirmStart | ISCSI_MIGRATE_CONFIRM | USECASE2 |
migrate.confirmSelection | ISCSI_MIGRATE_DB_CONFIRM | USECASE2 |
migrate.execute | ISCSI_MIGRATE_EXECUTE | USECASE2 |
attach.sqlInstance | ISCSI_SQL_INSTANCE | USECASE3 |
attach.dataDriveLetter | ISCSI_ATTACH_DATA_DRIVE | USECASE3 |
attach.logDriveLetter | ISCSI_ATTACH_LOG_DRIVE | USECASE3 |
attach.dbSelection | ISCSI_ATTACH_DB_SELECTION | USECASE3 |
attach.dbPrefix | ISCSI_ATTACH_DB_PREFIX | USECASE3 |
attach.attachAs | ISCSI_ATTACH_DB_NAME | USECASE3 |
attach.autoPair | ISCSI_ATTACH_AUTO_PAIR | USECASE3 |
attach.databases | ISCSI_ATTACH_DATABASES | USECASE3 |
attach.skipPartitionAssign | ISCSI_SKIP_PARTITION_ASSIGN | USECASE3 |
attach.diskWaitSeconds | ISCSI_DISK_WAIT_SECONDS | USECASE3 |
attach.diskOnlineTimeoutSeconds | ISCSI_DISK_ONLINE_TIMEOUT_SECONDS | USECASE3 |
unmount.sqlInstance | ISCSI_SQL_INSTANCE | UNMOUNT |
unmount.dbSelection | ISCSI_DETACH_DB_SELECTION | UNMOUNT |
unmount.dbPrefix | ISCSI_DETACH_DB_PREFIX | UNMOUNT |
unmount.disconnectTargets | ISCSI_DISCONNECT_TARGET_SELECTION | UNMOUNT |
unmount.fullCleanup | ISCSI_FULL_CLEANUP_CONFIRM | UNMOUNT |
unmount.removeTargets | ISCSI_REMOVE_TARGETS | UNMOUNT |
unmount.removePortals | ISCSI_REMOVE_PORTALS | UNMOUNT |
quiesce.sqlInstance | ISCSI_SQL_INSTANCE | QUIESCE |
quiesce.databases | ISCSI_QUIESCE_DATABASES | QUIESCE |
quiesce.allowSpanningDatabases | ISCSI_QUIESCE_ALLOW_SPANNING | QUIESCE |
quiesce.flushVolumeCache | ISCSI_QUIESCE_FLUSH | QUIESCE |
quiesce.requireSql | ISCSI_QUIESCE_REQUIRE_SQL | QUIESCE |
quiesce.stateTimeoutSeconds | ISCSI_STATE_TIMEOUT_SECONDS | QUIESCE |
resume.sqlInstance | ISCSI_SQL_INSTANCE | RESUME |
resume.databases | ISCSI_RESUME_DATABASES | RESUME |
resume.accessPaths | ISCSI_RESUME_ACCESS_PATHS | RESUME |
resume.waitSeconds | ISCSI_RESUME_WAIT_SECONDS | RESUME |
resume.repairMount | ISCSI_RESUME_REPAIR_MOUNT | RESUME |
resume.stateTimeoutSeconds | ISCSI_STATE_TIMEOUT_SECONDS | RESUME |
config.json in the toolkit nests fields under source and clone containers. The agent job config above uses the flat shape - portal and manualIQN at the top level, with migrate, attach, unmount, quiesce, and resume as blocks beside them. Do not wrap them in source or clone when creating a job.
Worked Examples
Every job is created the same way: POST /agent-job with the agent, the phase, and a config block, then poll until it settles.
POST /agent-job
GET /agent-job?job_id=<job_id>&include_events=1
A job settles at completed, failed, or cancelled. timed_out means the agent stopped reporting; an agent that comes back can still move the job to its real outcome. CHAP credentials, when needed, are sent as a secrets object beside config on the same POST - see CHAP.
Discover the host
Inventories the SQL host: instances with their versions, databases with their state, size, and recovery model, the volumes and drive letters in use, and the iSCSI portals, targets, and sessions. The defaults block carries the host's vME folder as vme_root, and the result also returns the connector's capabilities and the host facts.
{
"agent_id": "<source_agent_id>",
"phase": "DiscoverHost",
"config": { "portal": "10.0.0.10" }
}
Prepare LUNs and ingest in one job
SourcePrep connects the workflow's LUNs, formats them, and runs the migration. Give a long ingest the time it needs with job_timeout_seconds.
{
"agent_id": "<source_agent_id>",
"phase": "SourcePrep",
"config": {
"portal": "10.0.0.10",
"manualIQN": "iqn.2026-09.com.enov8:g-10057799540195812700",
"targets": ["iqn.2026-09.com.enov8:g-10057799540195812700"],
"mountRoot": "C:\\vME",
"mountId": "10057799540195812700",
"confirmPrep": true,
"migrate": {
"dbIds": "ALL",
"confirmStart": true,
"dbPrefix": "ISCSI_"
},
"job_timeout_seconds": 86400
}
}
To run the steps separately, use SourcePrepOnly with the top-level fields only, then SourceMigrateOnly with the migrate block and the same mountRoot and mountId.
Bodies for each migration mode
The migrate block above used backup and restore (dbIds, or dbNameFilter to select by name pattern). The other two modes swap the selection fields:
Restore from existing .bak files. Inventory the paths first with a probe - no folder or drive letters needed, nothing is restored:
{
"agent_id": "<source_agent_id>",
"phase": "SourceMigrateOnly",
"config": {
"migrate": {
"probeOnly": true,
"backupPaths": ["D:\\Backups\\*.bak"]
}
}
}
The result's backupSets lists each database found, whether it can be restored, and the files its set needs. Pass the files you want back as backupPaths on the real ingest:
{
"agent_id": "<source_agent_id>",
"phase": "SourceMigrateOnly",
"config": {
"mountRoot": "C:\\vME",
"mountId": "10057799540195812700",
"migrate": {
"backupPaths": [
"D:\\Backups\\sales_full_stripe1.bak",
"D:\\Backups\\sales_full_stripe2.bak",
"D:\\Backups\\sales_diff.bak"
],
"confirmStart": true,
"dbPrefix": "ISCSI_",
"sqlInstance": "HOST\\STAGING"
},
"job_timeout_seconds": 86400
}
}
sqlInstance is optional: omit it and the agent detects the instance.
Raw file copy. Offlines the database and copies its files onto the LUNs:
{ "mountRoot": "C:\\vME", "mountId": "10057799540195812700", "migrate": { "sourcePaths": { "dataPath": "D:\\Data\\sales.mdf", "logPath": "L:\\Logs\\sales_log.ldf" }, "confirmStart": true } }
Mount a clone
Run against the target appliance's agent, pointing manualIQN and targets at the clone's IQN and mountId at the clone's id. The attach applies the same prefix the ingest used, so the clone comes up under the name the ingest created.
{
"agent_id": "<target_agent_id>",
"phase": "CloneMount",
"config": {
"portal": "10.0.0.20",
"manualIQN": "iqn.2026-09.com.enov8:g-4185529034771265112",
"targets": ["iqn.2026-09.com.enov8:g-4185529034771265112"],
"mountRoot": "C:\\vME",
"mountId": "4185529034771265112",
"attach": {
"sqlInstance": "localhost",
"dbSelection": "ALL",
"autoPair": true,
"dbPrefix": "ISCSI_"
}
}
}
Tear down
CloneUnmount with MATCH scopes everything to the workflow's own LUNs: only its databases are detached and only its sessions disconnected. With mountRoot and mountId, the volume's folder is removed once its disks are gone. The same phase tears down a mounted clone (target agent, clone IQN and id) or the staging databases on the source host (source agent, source IQN and id):
{
"agent_id": "<target_agent_id>",
"phase": "CloneUnmount",
"config": {
"manualIQN": "iqn.2026-09.com.enov8:g-4185529034771265112",
"targets": ["iqn.2026-09.com.enov8:g-4185529034771265112"],
"mountRoot": "C:\\vME",
"mountId": "4185529034771265112",
"unmount": {
"dbSelection": "MATCH",
"disconnectTargets": "MATCH",
"fullCleanup": false,
"dbPrefix": "ISCSI_"
}
}
}
fullCleanup defaults to true when omitted and resets the iSCSI initiator host-wide. Always send it explicitly - false for anything scoped to one workflow.
Chained end to end
The Onboarding (API) folder in the vME Postman collection runs this whole sequence as ordered, chained requests, carrying job and workflow IDs between steps, including the vME-side snapshot, clone, and send calls between the agent jobs.