> ## Documentation Index
> Fetch the complete documentation index at: https://docs.insight.nobly.dk/llms.txt
> Use this file to discover all available pages before exploring further.

# Step reference

> Every step the ContentFlow workflow editor offers, grouped by purpose, with the inputs that matter and how each step behaves when it fails.

The step palette lists steps alphabetically. This page groups them by what they are for. Each input's information icon in the editor gives its exact description and type. The inputs below are named as the editor shows them.

Unless a step says otherwise, a step that fails sends the run to its [error path](/contentflow/design-and-publishing#handle-errors-with-the-exception-flow).

## Shape the document

| Step | What it does |
| - | - |
| **Map to Insight document** | Runs a [document mapping](/contentflow/document-mappings) and produces the document Nobly Insight receives: document type, document date and keywords, each with the reason for its value. The item's own properties are not changed. A mapping that stops the document fails the step with the explanation. |
| **Modify property** | Creates, copies, translates or removes one item property with a guided editor, without a script |

**Map to Insight document** has one input, `MappingName`: a list of the mappings, followed by classic mappers that no mapping has replaced. The step runs the mapping's published version; a test run uses the draft. Place it before the upload step; see [Use the mapping in a workflow](/contentflow/testing-and-publishing-mappings#use-the-mapping-in-a-workflow).

**Modify property** is for preparing data that a later *processing* step needs, such as a property that a decision or a lookup reads. Final keyword values belong in the mapping. Write to a property, then choose how its value is supplied:

* **Fixed value**
* **Copy** from another property
* **Translate** through a table, optionally a shared one
* **Remove**
* **Expression**

Its **Try it** card shows the result for a sample. **Save as test** keeps that sample as an [action test](/contentflow/testing-workflows#test-one-modify-property-step).

## Work with Nobly Insight documents

These steps use ContentFlow's own service account for Nobly Insight. That account's access rights decide which document types the steps can write.

| Step | What it does |
| - | - |
| **Upload to Nobly Insight** | Archives the item's file as a new document. After a Map to Insight document step, it sends the mapped document. |
| **Update keywords in Nobly Insight** | Replaces the keywords of an existing document |
| **Add revision to Nobly Insight** | Adds the item's file as a new revision of an existing document, with an optional comment of up to 250 characters |
| **Delete document in Nobly Insight** | Moves an existing document to the trashcan |
| **Upload JSON metadata to Nobly Insight** | Archives the item's metadata as a separate JSON document next to the uploaded one |

### Upload to Nobly Insight

After a Map to Insight document step, the upload sends the mapped document as it is: the step's `DocumentTypeId` and `DocumentDateTime` inputs are not applied. Without a mapping step, the upload keeps its earlier behaviour:

* the document type is the item's `ItemTypeName`, or `DocumentTypeId` when it is set
* keywords come from a classic mapper named in the step, or from properties named like keywords (`DynamicKeywordMapping`)

`FailOnUnmappedKeywords` decides whether a keyword the document type does not have fails the upload or is dropped. Leave it empty for the default: after a Map to Insight document step, strict unless the mapping passes properties through; with `DynamicKeywordMapping`, strict; with a classic mapper, lenient. Choose **Use step output** to make the new document available to later steps, for example a step that records its ID.

### Update keywords in Nobly Insight

`DocumentId` identifies the document, usually from an item property. There is no search by keyword values; resolve the ID earlier in the workflow.

After a Map to Insight document step, every keyword the mapping lists is managed. A listed keyword with a value for this document is set, and one without a value is cleared. Keywords the mapping does not list keep their current values. Without a mapping step, the step maps the item's complete metadata through a classic mapper, and the item must carry the complete metadata.

`DocumentTypeId` is an optional guard: when set, the step fails instead of writing if the document has another type. `SkipWorkflow` keeps the change from triggering Nobly Insight workflows. `SkipAutofillCoordination` writes autofill keyword groups exactly as sent.

### Delete document in Nobly Insight

A document that is already in the trashcan counts as deleted. A document Nobly Insight does not know fails the step, unless `TreatNotFoundAsSuccess` is set.

### Upload JSON metadata to Nobly Insight

Place it after the upload step. `Content` chooses what the JSON file holds:

* **Received**: the item as the run received it
* **Processed**: the item as it is at this step
* **Both**

The metadata document's type, date and keywords come from its own `MappingName`, or from `DocumentType` alone, without keywords. To link the two documents, give both mappings a row that writes the trace ID to the same keyword.

## Get or convert the file

| Step | What it does |
| - | - |
| **Fetch file from Azure Blob** | Fetches a file from an external Azure Blob Storage container and stores it as the item's file, for [metadata-only items](/contentflow/incoming-items#metadata-only-items). The file is stored as it is; it is never decrypted. |
| **Convert to PDF** | Converts the textual content of the item's file to a PDF and uses that as the item's file |

**Fetch file from Azure Blob** takes the storage endpoint (`BlobEndpoint`), container (`ContainerName`) and file name (`BlobName`), usually from an item property. The service principal that reads the container is given by the **names** of three global variables:

* `TenantIdVariable`
* `ClientIdVariable`
* `ClientSecretVariable`, which must be a secret variable

`TargetFileName` sets the item's file name. Give it a real extension, because the MIME type is derived from it unless you set `ContentMimeType`.

## Call other systems

| Step | What it does |
| - | - |
| **Webhook callback** | Tells another system that a document was archived, with a templated URL and body |
| **Http Request** | Sends a GET, PUT, POST or DELETE request and makes the response available to later steps |
| **SQL Query** | Runs a parameterised query against a SQL Server database and returns the result as JSON |
| **Send email** | Sends an email through an SMTP server, at most once per configured interval across all runs |

In all of these steps, an error from the other system, a timeout, or a failure to get a token fails the step. Decide in the exception flow whether that should fail the run or [schedule a retry](#control-the-run).

### Webhook callback

Place it right after the upload. `Url` and `Body` are templates with placeholders:

| Placeholder | Value |
| - | - |
| `{{documentId}}` | The Nobly Insight document ID, taken from the previous upload, revision, update or delete step unless `DocumentId` is set |
| `{{traceId}}` | The item's trace ID |
| `{{fileName}}`, `{{batchName}}`, `{{workflowName}}` | The item's file name, batch name and workflow name |
| `{{global:NAME}}` | The value of the global variable `NAME`, for example an API key |

Values are encoded for where they land: percent-encoded in the URL, and JSON-escaped in the body and headers. You supply the quotes in a JSON body. An empty `Body` sends a default payload with the document ID, trace ID, workflow name, file name and batch name. `HeadersJson` adds request headers. `Method` is POST, PUT or PATCH.

### Authenticate with OAuth client credentials

**Webhook callback** and **Http Request** can authenticate with the OAuth 2.0 client-credentials grant against the other system's token endpoint. Set `TokenEndpoint`, and give the **name** of a global variable for each credential, never the credential itself:

| Input | Holds |
| - | - |
| `ClientIdVariable` | The name of the global variable with the client ID |
| `ClientSecretVariable` | The name of a **secret** global variable with the client secret |
| `ClientAssertionKeyVariable` and `ClientAssertionKid` | Instead of a secret: the name of a secret global variable with a private key that signs a client assertion, and the key ID the identity provider knows it by. Use this when the provider requires signed assertions (`private_key_jwt`). |
| `Scope` | The scope to request, if the provider requires one |

Tokens are cached and reused until shortly before they expire, so a batch of thousands of items does not request a token per item. If the other system answers 401 to an **Http Request**, ContentFlow requests a fresh token and retries once. A **Webhook callback** that gets a 401 fails.

**Http Request** can instead authenticate as ContentFlow's Nobly Insight service account, for calls to the Nobly Insight API that no dedicated step covers.

### SQL Query

The connection string comes from a global variable named in `ConnectionVariable`; make it a secret variable. Put values in `@name` parameters through `ParametersJson`, never by building the query text from item data. `ResultMode` is one of:

* `Scalar`: the first column of the first row
* `FirstRow`
* `AllRows`

`MaxRows` caps the result. The step fails rather than truncating it. The step is read-only unless `AllowWrites` is set.

### Send email

The SMTP password is a step input; give it from a secret global variable rather than as literal text. `RateLimitInMinutes` limits how often the step sends across all runs, for example one alert per hour during a failing batch.

## Control the run

| Step | What it does |
| - | - |
| **Completed** | Ends the run as completed, and removes its temporary file |
| **Failed** | Ends the run as failed, with an optional `FailedReason` |
| **Schedule retry** | Ends this attempt and retries the run after `ScheduleAfterMinutes`, from the start or, with `RetryFromLastStep`, from the last step. After `MaxRetries` retries, the run is marked failed instead. |
| **Start workflow** | Hands the workflow data, including the trace ID, to another workflow, which continues the run. The run's details then show that workflow as its current workflow. |
| **Throw exception** | Fails on purpose, to send the run down its error path |
| **Do nothing** | Has no effect; useful as a placeholder or a join point |

## Record and diagnose

| Step | What it does |
| - | - |
| **Log message** | Writes a message to the run's log, which you read on the [States](/contentflow/states-and-recovery) page |
| **Add state result property** | Records a name and value on the run. The sending system reads result properties with the item's state, and they show on the run's **Overview** tab. |
| **Script** | Runs C# code, for logic no step covers. Scripts can call other systems through the `http` helper; see [Variables and tools](/contentflow/variables-and-tools#call-other-systems-from-a-script). |

Prefer a dedicated step to a script where one exists: a step is visible on the canvas, validated, and needs no code review.

## Steps kept for older versions

Some steps are no longer offered, but saved workflow versions that use them keep running:

* **Derive document type** is replaced by the document type rules in a [document mapping](/contentflow/document-mappings#document-type).
* **RemoveProperty**, **ReplacePropertyValue**, **SplitPropertyValues**, **PadPropertyValues** and **RegexReplaceValue** are replaced by Modify property and the changes on mapping rows.
* **MapUdmToNoblyInsightModel** is replaced by Map to Insight document.
* **Test** and **PadString** have no replacement.

The editor keeps such a step's type selectable on the step that uses it, so that older workflows stay editable. Replace the step when you next change the workflow, and test the result before you publish it.

## Where to read next

<Card title="Testing workflows" icon="vial" href="/contentflow/testing-workflows" horizontal>
  Run a sample through the draft workflow and its draft mappings, and compare the result with what you expect.
</Card>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.