# Welcome to Insly AI

These pages serve to explain Insly's AI-driven products.&#x20;

Should you have further questions, please reach out to us:

* Diana Veider – AI Business Lead – <diana.veider@insly.com>
* Roald Sinissaar – AI Product Owner – <roald.sinissaar@insly.com>

### Explore:

<table data-view="cards"><thead><tr><th></th><th></th><th></th><th data-hidden data-card-cover data-type="files"></th><th data-hidden></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td><h4><i class="fa-bolt">:bolt:</i></h4></td><td><strong>Nora</strong></td><td>Insly's format-agnostic data extraction engine for claims and underwriting processes</td><td></td><td></td><td><a href="/pages/i73g4LZQanoLj7XtSO18">/pages/i73g4LZQanoLj7XtSO18</a></td></tr></tbody></table>


# Introduction

Nora is an AI layer that augments insurance operations. It extracts, understands, follows up, triages, and orchestrates workflows across submissions, underwriting, and claims. Modular by design, Nora integrates with existing systems and can be deployed via API, MCP, or embedded workflows.


# Terminology

### Submission <a href="#submission" id="submission"></a>

End users create Submissions, which are some collection of input that will be processed by AI, reviewed and confirmed by a human, and be exposed to downstream processes. One may be created via the web UI by submitting files, or by sending an email with attachments to a predefined email address.

A Submission may have one or more Files attached, and will require a Template to be processed.

### File <a href="#file" id="file"></a>

A document submitted by an end user. Currently, Nora accepts the following file formats:

* pdf
* doc, docx
* csv
* xls, xlsx
* gif, png, jpg or jpeg, heic

Documents may be converted to PDFs for processing and display, which means some files (notably Excel spreadsheets) may look different after processing. This does not affect the quality of data extraction.

### Template <a href="#template" id="template"></a>

Each submission must have a template assigned - this is to guide the AI agent in extraction, and defines the fields and structure that you wish to extract. Fields may be text or number fields, take single values or an enum of different values.

You may also define discrete sections and allow lists of multiple objects.

Each field in the template also takes an optional "AI hint", which aids the AI agent in data extraction. In many cases, this is not needed as the structure and field titles are descriptive enough (e.g. `Applicant` > `First name` is plenty), but it can be helpful in other cases (e.g. `Applicant` > `Country of residence` may take the hint `If this is not stated in the source document, infer it from the correspondence address`)

The "shape" of the template also dictates how the final JSON-formatted payload will look. For example:

<figure><img src="/files/KTSHrkk6Cl3PuAGK3jOB" alt=""><figcaption></figcaption></figure>

Ex:

* Curly braces {} indicate key in payload.
* Single arrow down represents a JSON object.
* Double arrow down represents an array of objects.
* *Aa* means the field is a string

This template in example could result in payload structured like:

```json
{
  "customer": {
    "firstName": "John",
    "surname": "Wick"
  },
  "risk": {
    "zip": "12345",
    "street": "Main Street",
    "city": "New York",
    "country": "USA",
    "county": "New York"
  },
  "valueableItems": [
    {
      "name": "Gold",
      "value": "10000"
    },
    {
      "name": "Diamonds",
      "value": "I think about 4000$"
    },
    {
      "name": "Fake Rolex",
      "value": "100 - more sentimental value"
    }
  ]
}
```


# Skills


# Email intake and Email whitelisting

### Email intake

The simplest way to submit information to Nora is by email. Brokers can send their submissions directly to a dedicated email address, and Nora will automatically extract and structure the data — no login required, no forms to fill out, no learning curve.

This means brokers can continue working the way they always have, while your team benefits from automated processing on the backend.

The submission is instantly available in the Nora UI, where another team member can pick it up and validate it. Email intake also supports workflows where the submitter and the processor are different people. A broker can email in a case, and your internal team handles everything from there.

It's fast, flexible, and removes friction from the submission process.

Email intake is available on all plans and is pre-configured out of the box.

### Email whitelisting

Not everybody should be able to commandeer your time and attention. Nora gives you control over who can submit via email.

You can define which domains or individual email addresses are allowed to send submissions into the system. Only messages from whitelisted senders will be processed — everything else is ignored.

This keeps your intake clean, your workflow focused, and your team protected from spam or irrelevant submissions.

Whitelisting can be configured at the domain level (e.g., all addresses from `@brokerfirm.com`) or for specific individual addresses. You have full flexibility to adjust the list as your broker relationships evolve.

It's a simple safeguard that ensures only trusted partners can access your underwriting pipeline.

{% hint style="info" %}
For information on how to set up this feature, please [reach out to us](/). Email whitelisting is available on the M plan and upwards.
{% endhint %}


# Role-based access

Not everyone needs to see everything. FormFlow lets you control exactly what each user can access based on their role.

Internal team members — underwriters, operations staff, managers — can be assigned as full members with complete visibility across all submissions and full control over the platform.

External users, like brokers, can be assigned as viewers. They only see the submissions they've personally sent in. Nothing else. This keeps your pipeline confidential while still giving brokers transparency into the status of their own cases.

This means brokers can check in on their submissions without needing to call or email for updates — and your team doesn't need to worry about exposing sensitive data or competitive intelligence.

Role-based access ensures the right people see the right information, and nothing more.

For information on how to set up roles and permissions, see the configuration guide, or reach out to us. Role-based access is available on the M plan and upwards.

{% hint style="info" %}
For information on how to set up this feature, please [reach out to us](/). RBAC is available on the L plan and by custom agreement.
{% endhint %}


# Chat agent integration

FormFlow can meet your users wherever they already are. Our chat agent can be embedded directly into other SaaS platforms, giving brokers or clients a conversational way to submit information without ever leaving the environment they're working in.

The chat agent understands natural language, asks clarifying questions when needed, and processes submissions in real time using FormFlow's extraction engine. The data flows seamlessly into your underwriting pipeline, structured and ready to action.

This makes FormFlow invisible to the end user — they just have a conversation, and the submission gets handled automatically on your side.

#### **Special integration with Insly Ledger**

For users of Insly Ledger, we've built a deeper level of integration. Once a submission is processed and a quote is generated, the agent can navigate the user directly to the quote within the Ledger UI.

The agent can review quote fields in-browser and suggest edits where needed. It can also work with the user to add any additional information to the quote, all through natural conversation.

The result is a completely unified experience — brokers submit, review, and refine quotes without switching tools or waiting for back-and-forth emails.

{% hint style="info" %}
For a demo or information on how to set this up, please [reach out to us](/). Agentic experiences are available on L and Enterprise plans, and require an Insurance Copilot licence.
{% endhint %}


# Email Follow Up

One of the biggest bottlenecks for underwriters is chasing brokers for missing information. When a submission arrives without all the required details, underwriters cannot proceed with creating a quote. This creates a back-and-forth “email ping-pong” that can easily waste hours and stall deals.

Nora removes this pain completely.

* If required values are missing after extraction, Nora automatically sends an email to the submitting broker.
* The email includes a clear reference to which fields are missing. The template and wording are fully customizable to match your tone and process.
* The broker simply replies to the email — either itemised or freeform — and the system re-processes the response.
* Missing values are automatically added to the submission, and the underwriter gets a notification that the case is now complete and ready to quote.

All of this happens without the underwriter lifting a finger. The email does not land in their inbox; it goes directly into Nora, where the submission is enriched and marked as complete.

**This feature eliminates:**

* repetitive manual communication,
* speeds up quoting,
* ensures brokers get a faster response.

Underwriters save valuable time, reduce errors, and focus on what really matters: assessing risk and writing more business.

{% hint style="info" %}
For information on how to set up this feature, see the [configuration guide](/nora/configuration-guide/set-up-email-follow-ups), or [reach out to us](/). Email follow-ups are available on the **Standard** **plan** and upwards.
{% endhint %}


# Configuration guide

Formflow has first-party integration support for Insly Ledger. The integration allows for:

* manual or automatic export of submissions
* [email follow-up](/nora/configuration-guide/set-up-email-follow-ups) functionality with auto-export
* token sharing and federated authentication for Insly Agents

To configure it, navigate to the settings page and locate the custom integration settings. You will want to add a block with `type: "ledger"` defined, for example:

```json
[
  {
    "type": "ledger",
    "settings": {
      "scope": "auth/tenant",
      "tenant": "tenant",
      "baseUrl": "https://tenant.app.beta.insly.training",
      "clientId": "xxxredactedxxx",
      "clientSecret": "xxxredactedxxxxxxredactedxxx"
    }
  },
  // other integration blocks
]
```


# Setting up webhooks

The [Webhook API](/nora/api-documentation/webhooks) allows you to receive real-time notifications when specific events occur in your FormFlow submissions. By subscribing to webhook events, your application can automatically respond to submission processing milestones without polling.

### Available Event Types

The webhook system supports six event types:

<table><thead><tr><th width="252.2890625">Event</th><th>Triggered When</th></tr></thead><tbody><tr><td><code>extracted</code></td><td>AI extraction of submission data completes successfully</td></tr><tr><td><code>export</code></td><td>Manual export is initiated for a submission</td></tr><tr><td><code>validation-success</code></td><td>Submission validation passes before export</td></tr><tr><td><code>error-export</code></td><td>Triggered when export fails (both automatic and manual exports)</td></tr><tr><td><code>error-processing</code></td><td>Triggered when submission processing/extraction fails</td></tr><tr><td><code>error-pre-processing</code></td><td>Triggered when email submission pre-processing fails</td></tr></tbody></table>

All webhook events share a common payload structure:

```json
{
  "eventId": "123e4567-e89b-12d3-a456-426614174000",
  "eventType": "extracted",
  "timestamp": "2023-10-28T12:00:00.000Z",
  "submissionId": "123e4567-e89b-12d3-a456-426614174001",
  "templateId": 1
}
```

### Webhook Security

When creating a subscription for an event, you will receive a `secret` token which you can use to verify whether the webhook is sent by us and is unchanged.

When we send out webhook events a signature is included in the header. This is created by hashing the payload `secret` before transport. You can use the token to hash the payload on your end using **HMAC-SHA256** (hex-encoding) and compare if the signatures match to confirm that the payload is valid and unchanged.

{% hint style="warning" %}
**Important**: You must use the exact raw request body string to compute the hash, not a re-serialized version of the parsed JSON. Different JSON serialization can produce different strings even for the same data.
{% endhint %}

#### **Signature Headers**

<table><thead><tr><th width="205.23828125">Header</th><th>Description</th><th>Example</th></tr></thead><tbody><tr><td><code>X-Webhook-Signature</code></td><td>HMAC-SHA256 signature (hex-encoded)</td><td><code>a3b2c1d4e5f6...</code></td></tr><tr><td><code>X-Webhook-Timestamp</code></td><td>ISO 8601 timestamp when signature was generated</td><td><code>2023-10-28T12:00:00.000Z</code></td></tr></tbody></table>

### Receiving Webhooks

#### Endpoint Requirements

Your webhook endpoint should:

1. **Respond quickly** - Return a 2xx status code within 5 seconds
2. **Be publicly accessible** - The webhook service must be able to reach your endpoint
3. **Handle idempotency** - The same event may be delivered multiple timesx

Below is an example on how verification works.

```javascript
const crypto = require('crypto');

function verifyWebhookSignature(rawBody, signature, secret) {
  const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(rawBody, 'utf8')
    .digest('hex');
  
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expectedSignature)
  );
}

// Here we have a separate endpoint for an event, it is also possible to
// create logic that chooses the correct secret based on the "type" field
app.post('/webhooks/formflow/extracted', (req, res) => {
  const signature = req.headers['x-webhook-signature'];
  
  
  const secret = process.env.WEBHOOK_SECRET_EXTRACTED;
  
  if (!signature || !verifyWebhookSignature(req.body, signature, secret)) {
    return res.status(401).json({ error: 'Invalid signature' });
  }
  
  // Process verified webhook...
});
```


# Process a submission via API

This guide explains how to process a submission using the FormFlow API.&#x20;

The sequence involves creating a submission, uploading files, and initiating processing. By the end of the process, the submission will be in the "ready for review" status.

The steps to process a typical submission are as follows:

{% stepper %}
{% step %}

### Create an empty submission

Call [`POST /api/submission`](broken://pages/9759ca3f5fdf53f3395ef66b2964fde0c0e0db92#post-submission) to create an empty submission.

{% endstep %}

{% step %}

### Upload files

Use `POST /api/submission/{id}/upload` to retrieve a secure file upload URL for each file in your submission. `{id}` refers to the submission ID from step 1.

The `contentType` parameter in the request body must be one of the following:

```
application/pdf
application/msword
application/vnd.openxmlformats-officedocument.wordprocessingml.document
application/vnd.ms-excel
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
image/jpeg
image/heic

```

`PUT` the files to the URLs. You will need to do multipart uploading for files exceeding 5MB. Only proceed after all files are finished uploading.

{% endstep %}

{% step %}

### Initiate processing

Call [`POST /api/processing`](/nora/api-documentation/processing#post-processing) to initate processing. The response payload will include a unique processing event ID, which you can use in step 4:

```json
{
  "eventId": "string",
  "submissionId": "string",
  "message": "The submission has been queued for processing. You may GET the event status using the provided event ID."
}
```

{% endstep %}

{% step %}

### Check processing status

You can check `GET /api/processing/{id}` to check on the processing event. The response will include a `state` value:

```json
{
  "id": "string",
  "submissionId": "string",
  "type": "extraction",
  "state": "pending",
  "createdAt": "2025-04-07T12:18:24.000Z",
  "updatedAt": "2025-04-07T12:18:24.000Z"
}
```

{% endstep %}

{% step %}

### Retrieve processed submission

Finally, when the processing event is in the `processed`  state, you can `GET /api/submission/{id}` to retrieve the full submission to inspect its `payload`:

```json
{
  "id": "string",
  "submissionNumber": "number",
  "name": "string",
  "template": {
    // Template details
  },
  "templateId": "number",
  "uploaderUserId": "string",
  "userId": "string",
  "files": [
    // List of files
  ],
  "payload": {
    // structed data gathered from files
  }
  "status": "processed",
  "createdAt": "string",
  "updatedAt": "string"
}
```

{% endstep %}
{% endstepper %}


# Create a submission export flow

When a user or automation marks a Formflow submission as "completed", then you likely want to make use of the data somewhere else.&#x20;

It is straightforward to set up a custom export flow. There are a number of helpful third party services for such processing, including [n8n](https://n8n.io/), [Zapier](https://zapier.com/) and [IFTTT](https://ifttt.com/), or you can roll your own solution.&#x20;

All approaches can make use of Formflow's [webhook](/nora/api-documentation/webhooks) notifications, [API](/nora/api-documentation) and MCP services.

### Be notified of new submissions

You will want to react to new submissions in the `completed` status. There are two ways to automate this:

* Regularly poll [`GET /submission`](broken://pages/9759ca3f5fdf53f3395ef66b2964fde0c0e0db92#get-submission) for a list of the most recent submissions, maintain a list of processed submissions, and choose which submissions to process
* Register a [webhook subscription](/nora/api-documentation/webhooks#post-webhook-subscription). In this case, Formflow will `POST` at your designated endpoint and include a submission ID.

### Fetching completed submissions

Regardless of how you became aware of a new completed submission, you will want to [`GET /submission/{id}`](broken://pages/9759ca3f5fdf53f3395ef66b2964fde0c0e0db92#get-submission-id) using the unique submission ID. The response will include:

* submission metadata
* presigned temporary file download URLs (if configured)
* the complete data payload in JSON format

You can then process the submission `payload` per your business logic.&#x20;


# Set up email follow-ups

It is possible to configure Formflow in such a way that the system automatically prompts the submitting user about missing values over email. This is useful in situations where submissions may come from parties who will not be accessing the web UI at all.

In this scenario, a submission will be marked as "completed" automatically when all required fields are present in the extracted payload.

### Submission flow

{% columns %}
{% column %}
If, after extraction, there are required values missing, an email is sent back to the submitting user.&#x20;

A reference to the missing fields will be provided. The user may reply to the email how they like: as an itemised response, or as a freeform reply.&#x20;

The email is re-processed and additional data points are added to the submission.

The submission is marked as `completed` once all required fields are set, and it will be exported to Ledger as a new quote. The user will receive a "success" email.

The email loop will not be triggered if all required values are present immediately: in that case the submission will instantly be marked as completed. This will also happen in case there are no required fields set.
{% endcolumn %}

{% column %}

<figure><img src="/files/2oetgi9xlHYMAa9nw1fX" alt="" width="375"><figcaption></figcaption></figure>

{% endcolumn %}
{% endcolumns %}

### Configuring follow-ups:

Go to Formflow Settings. Locate the custom configurations section, and add a new property `enableFollowUp` and define it as `true`, for example:

```json
[
  {
    "type": "ledger",
    //
    // other 'settings' omitted...
    //
    "enableFollowUp": true,
    "export-by-default": true
  }
]
```


# Email notifications

FormFlow sends automated email notifications through SendGrid using dynamic templates. There are **4 message categories** that you should create custom templates for:&#x20;

* `SUCCESS`
* `VALIDATION`
* `ERROR`
* `REJECTION`

Each category can have its own SendGrid template with custom branding and styling.&#x20;

In addition to a `message_category`, each message will also have a `message_type`, and you will want to handle different types of messages with in-template logic, as well as define a fallback condition to send a generic email in case you encounter a new type of email in the future.

## Email Notification Types

### Success emails

Positive confirmations as feedback to the user that all is going according to plan.

<details>

<summary>Processing has begun</summary>

If an email is well received, we are able to pull all of its attachments, and all attachments are valid, then we reply to user to say that their submission is being processed.

**Example Payload:**

```json
{
  "subject": "Your Submission is Being Processed",
  "message_category": "SUCCESS",
  "message_type": "submission_started",
  "submission_id": "ab761b6e-c9ba-41ef-9d08-b41c4e1de35b",
  "suggested_message": "Thank you for your submission. We have successfully received your documents and processing has begun. You will be notified once the process is complete.",
  "data": {}
}
```

</details>

<details>

<summary>Submission completed</summary>

If email loopback functionality is enabled, and Ledger integration is configured, and the submission is completed, and it has been successfully exported to Ledger, we inform the user of this.

**Example Payload:**

```json
{
  "subject": "Submission Processed Successfully",
  "message_category": "SUCCESS",
  "message_type": "submission_completed",
  "submission_id": "ab761b6e-c9ba-41ef-9d08-b41c4e1de35b",
  "suggested_message": "Your submission has been successfully processed and exported to our system. Thank you for using FormFlow.",
  "data": {}
}
```

</details>

### Validation emails

Information requests – something about a submission is incomplete and requires the submitting user's attention.

<details>

<summary>Missing fields</summary>

If email loopback functionality is enabled and an extraction is complete, but there are validation errors.

**Example Payload:**

```json
{
  "subject": "Additional Information Required - Submission #12345",
  "message_category": "VALIDATION",
  "message_type": "missing_fields",
  "submission_id": "ab761b6e-c9ba-41ef-9d08-b41c4e1de35b",
  "suggested_message": "Thank you for your submission. We have reviewed your documents and found that some required information is missing or incomplete. Please review the items listed below and resubmit your documents with the necessary details.",
  "data": {
    "missing_fields": [
      "Policy holder's date of birth",
      "Vehicle registration number",
      "Proof of no claims bonus",
      "Current insurance certificate expiration date"
    ]
  }
}
```

</details>

### Rejection emails

Active rejections: something about the submisison is unsatisfactory. It is likely that the user can do something about the situation.

<details>

<summary>Unsupported attachments</summary>

If one or more attachments are not supported, then we advise the user that their submission will not be processed.

**Example Payload:**

```json
{
  "subject": "Submission Not Supported",
  "message_category": "REJECTION",
  "message_type": "unsupported",
  "submission_id": "ab761b6e-c9ba-41ef-9d08-b41c4e1de35b",
  "suggested_message": "Thank you for your submission. Unfortunately, we are unable to process the documents you have submitted as they do not match the required format or document type we currently support.",
  "data": {
    "rejection_reasons": [
      "The submitted documents are in an unsupported file format",
      "Only PDF, PNG, and JPEG files are accepted",
      "Please resubmit your documents in a supported format"
    ]
  }
}
```

</details>

<details>

<summary>Whitelist rejection</summary>

If email whitelisting is configured, and the emailing user is not whitelisted by domain or email address, then we advise the user that their submission is not accepted.

**Example Payload:**

```json
{
  "subject": "Access Restricted",
  "message_category": "REJECTION",
  "message_type": "whitelist",
  "submission_id": "no-submission",
  "suggested_message": "We appreciate your interest. However, access to this service is currently limited to authorized partners and pre-approved organizations.",
  "data": {
    "rejection_reasons": [
      "Your email address or domain is not currently authorized",
      "Please contact your account manager to request access"
    ]
  }
}
```

</details>

### Error emails

Advisory email regarding processing failures. In these cases it is unlikely that the user can do something, and they should probably contact a support channel.

<details>

<summary>Pre-processing error</summary>

If there is an unexpected issue reading the incoming email, then we respond with a generic error message.

**Example Payload:**

```json
{
  "subject": "Error Reading Your Submission",
  "message_category": "ERROR",
  "message_type": "preprocessing",
  "submission_id": "no-submission",
  "suggested_message": "We encountered an issue while reading your email submission. Please try resubmitting your documents, or contact support if the problem persists.",
  "data": {
    "error_messages": [
      "Unable to parse email content",
      "Attachment extraction failed"
    ]
  }
}
```

</details>

<details>

<summary>Processing error</summary>

If there is an issue during processing, then we respond with an error message, detailing the submission ID, failure stage and a short error stack.

**Example Payload:**

```json
{
  "subject": "Submission Processing Failed",
  "message_category": "ERROR",
  "message_type": "processing",
  "submission_id": "ab761b6e-c9ba-41ef-9d08-b41c4e1de35b",
  "suggested_message": "We encountered an issue while processing your submission. Our technical team has been notified and is investigating the problem.",
  "data": {
    "error_messages": [
      "There was a problem during processing, and your documents have not been processed.\n Processing Step: convertFiles.\n Error message: Some error message.\n Submission ID: ab761b6e-c9ba-41ef-9d08-b41c4e1de35b.\n If the issue persists, please contact your Account Manager."
    ]
  }
}
```

</details>

<details>

<summary>Export error</summary>

If Ledger integration is configured, but exporting a submission fails even after it passes validation, then we respond with a generic error message.

**Example Payload:**

```json
{
  "subject": "Export to System Failed",
  "message_category": "ERROR",
  "message_type": "export",
  "submission_id": "ab761b6e-c9ba-41ef-9d08-b41c4e1de35b",
  "suggested_message": "Your submission was processed successfully, but we encountered an issue exporting it to our external system. Our team has been notified and will resolve this shortly.",
  "data": {
    "error_messages": [
      "Export to Ledger failed",
      "Connection timeout to external service",
    ]
  }
}
```

</details>

***

## Setting Up SendGrid Templates

FormFlow uses SendGrid's dynamic templates to send emails. Each template uses Handlebars syntax to dynamically insert data from the payload.

### Template Structure

All email payloads follow a standardized, flattened structure for SendGrid:

```typescript
{
  subject: string;              // Email subject line
  message_category: string;     // SUCCESS, VALIDATION, ERROR, or REJECTION
  message_type: string;         // Specific type within category
  submission_id: string;        // Unique submission identifier
  suggested_message: string;    // Main message body
  data: {                       // Optional additional data
    // Category-specific fields
  }
}
```

### Example: Validation Template Setup

**1. Create the HTML Template in SendGrid:**

```html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Validation - Missing Information</title>
</head>
<body style="margin: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Arial, sans-serif; background-color: #e8e8e8;">
    <table role="presentation" style="width: 100%; border-collapse: collapse;">
        <tr>
            <td align="center" style="padding: 40px 20px;">
                <table role="presentation" style="max-width: 700px; width: 100%; border-collapse: collapse;">
                    <!-- Logo -->
                    <tr>
                        <td style="padding: 0 0 30px 0;">
                            <img src="https://insly.com/wp-content/uploads/2024/06/Logo-with-text-4.png" alt="Insly" style="height: 40px; display: block;">
                        </td>
                    </tr>
                    
                    <!-- Content Card -->
                    <tr>
                        <td style="background-color: #ffffff; border-radius: 8px; padding: 50px 40px; box-shadow: 0 2px 8px rgba(0,0,0,0.08);">
                            <h1 style="margin: 0 0 30px 0; color: #1a1a1a; font-size: 28px; font-weight: 600; line-height: 1.3;">
                                Additional information is required
                            </h1>
                            
                            <!-- Dynamic suggested message -->
                            <p style="margin: 0 0 20px 0; color: #333333; font-size: 18px; line-height: 1.6;">
                                {{suggested_message}}
                            </p>
                            
                            <!-- Conditional missing fields list -->
                            {{#if data.missing_fields}}
                            <p style="margin: 0 0 15px 0; color: #333333; font-size: 18px; line-height: 1.6;">
                                The following fields are missing or incomplete:
                            </p>
                            <ul style="margin: 0 0 20px 0; padding-left: 25px; color: #333333; font-size: 18px; line-height: 1.8;">
                                {{#each data.missing_fields}}
                                <li>{{this}}</li>
                                {{/each}}
                            </ul>
                            {{/if}}
                            
                            <p style="margin: 25px 0 0 0; color: #333333; font-size: 18px; line-height: 1.6;">
                                Please provide the missing information and resubmit your documents.
                            </p>
                        </td>
                    </tr>
                    
                    <!-- Footer -->
                    <tr>
                        <td style="padding: 30px 0 0 0;">
                            <p style="margin: 0 0 10px 0; color: #888888; font-size: 15px; line-height: 1.6;">
                                This automated email was sent by FormFlow, and responses will not be read.
                            </p>
                            <p style="margin: 0; color: #888888; font-size: 15px; line-height: 1.6;">
                                With questions, please reach out to your customer success representative, or to 
                                <a href="mailto:info@insly.com" style="color: #888888; text-decoration: underline;">info@insly.com</a>
                            </p>
                        </td>
                    </tr>
                </table>
            </td>
        </tr>
    </table>
</body>
</html>
```

**2. Understanding Handlebars Syntax:**

* `{{variable}}` - Inserts the value of a variable
* `{{#if condition}}...{{/if}}` - Conditional rendering
* `{{#each array}}...{{/each}}` - Loop through array items
* `{{this}}` - Current item in a loop

**3. Key Template Variables:**

All templates have access to these flattened variables:

* `{{subject}}` - Email subject
* `{{message_category}}` - Category (SUCCESS, VALIDATION, ERROR, REJECTION)
* `{{message_type}}` - Specific message type
* `{{submission_id}}` - Submission identifier
* `{{suggested_message}}` - Main message content
* `{{data}}` - Object containing category-specific fields

**4. Category-Specific Data Fields:**

**Validation emails:**

```handlebars
{{data.missing_fields}} - Array of missing field names
```

**Error emails:**

```handlebars
{{data.error_messages}} - Array of error message strings
```

**Rejection emails:**

```handlebars
{{data.rejection_reasons}} - Array of rejection reason strings
```

**Success emails:**

```handlebars
{{data.confirmation_details}} - Array of confirmation message strings
```

**5. Configure in Organization Settings:**

Inside FormFlow's settings tab editor you can assign the SendGrid template IDs to their respective message categories.  \
\
Here you can see the `templates` object has truncated SendGrid template IDs, you should include yours in full.

```json
{
  "sendgrid": {
    "api_key": "SG.xxxxx",
    "from_email": "noreply@yourcompany.com",
    "from_name": "FormFlow",
    "templates": {
      "validation": "d-abc123...",
      "error": "d-def456...",
      "rejection": "d-ghi789...",
      "success": "d-jkl012...",
      "fallback": "d-mno345..." // this one has to be set
    }
  }
}
```

**6. Testing Your Template:**

Use SendGrid's template editor test feature with this sample data:

```json
{
  "subject": "Test Validation Email",
  "message_category": "VALIDATION",
  "message_type": "missing_fields",
  "submission_id": "test_12345",
  "suggested_message": "This is a test validation message.",
  "data": {
    "missing_fields": [
      "Test Field 1",
      "Test Field 2"
    ]
  }
}
```

### Best Practices

1. **Always provide fallback content** - Use `{{#if}}` blocks to handle missing data gracefully
2. **Keep inline styles** - Email clients have limited CSS support
3. **Test across email clients** - Use services like Litmus or Email on Acid
4. **Use semantic HTML** - Tables for layout (email client compatibility)
5. **Optimize images** - Host externally and use absolute URLs
6. **Keep file size small** - Some email clients truncate large emails


# Insly Ledger integration


# Setting up configuration

{% hint style="info" %}
API crentials can be issued by Ledger support team representatives
{% endhint %}

To set up Ledger integration you will need need to add "ledger" type settings object to an array of settings.

Here is an example of the setting:

```json
[
  {
    "type": "ledger",
    "settings": {
      "env": "environment-in-ledger",
      "scope": "auth/your-tenant-name",
      "tenant": "your-tenant-name",
      "clientId": "your-client-id",
      "clientSecret": "your-client-secret",
      "brokerShortName": "your-broker-name" // optional
    }
  },
  // other existing settings here
]
```

Settings description:

| Setting         | Type   | Description                                                                 |
| --------------- | ------ | --------------------------------------------------------------------------- |
| env             | string | enum; can be either "prod", "demo" or "beta"                                |
| scope           | string | scope is always your tenant name with a prefix of "auth/"                   |
| clientId        | string | issued by Ledger                                                            |
| clientSecret    | string | issued by Ledger                                                            |
| brokerShortName | string | optional; set it only if you wish to create quote under one specific broker |


# Import and update templates

If integrated with Insly Ledger, then you will have the ability to import schemas from Ledger into Formflow as templates. The specified schema version will become the export destination.

### Modifications made during import

#### **Unwanted root properties are removed**

There are some pre-defined fields which are not needed for data extraction, and are therefore removed:&#x20;

```
notes
documents
schedules
endorsements
referralComments
underwritingNotes
underwriteNotes
underwriterNotes
referralRules
```

#### `$ref` references are resolved

Any `$ref` pointers are resolved to the actual definition objects. Formflow is RJSF-friendly, but not fully compliant. The Formflow template may therefore include repeated fields.

#### Only required and supported parameters are copied

We copy over only selected fields and recursively process nested `properties` and array `items`:

```
schema.type
schema.title
schema.description
schema.enum
schema.enumNames
schema.minItems
schema.maxItems
schema.format
```

*Note:* `minItems` and `maxItems` are kept for the Formflow template schema but removed before AI extraction.

#### **Dependencies and** `oneOf` **conditions are merged**

For example, if choosing *Option 1* reveals *sub-option 1* and *sub-option 2* in `sectionB`, those values are merged directly into `sectionB`, and the dependencies are discarded.

#### **All enums are combined**

We merge all enum possibilities into a single, unified enum.


# Exporting to Ledger

It is a good idea to test many different submissions in demo → beta to encounter as many validation issues as possible. Formflow does hold a copy of all dependency requirements and does its best to validate all related requirements, but very complex schemas deserve thorough testing.

### "No binder available for quote"

This issue appears if Ledger is unable to assign a binder. This is chosen based on three key pieces of data:

* `broker.shortName` – this is pulled from the payload, and the field must remain in the Formflow template. Make sure it is an enum with allowed values and remember that suitable values may be different between pre-production and production environments.
* `policy.dateInception` – this date field is pulled from the payload. It must exist, be valid, and fall within the start and end dates of a valid binder.
* `schema.name` – this is passed with the template if the Ledger integration is correctly configured, and is unlikley to be the issue.


# Multi-factor authentication

When using Formflow, each user has the ability to set up multi-factor authentication. The accepted method is a TOTP-based authentication application, e.g. 1Password or Google Authenticator.

To set up MFA, navigate to your profile view from the top right hand corners, and open "Security". Look out for "**Two-step verification**": this allows you to go into the MFA setup flow.

<figure><img src="/files/rRMxHNl797zBOVW2HUm2" alt=""><figcaption></figcaption></figure>

{% hint style="info" %}
As an organisation leader, you may request that MFA is made mandatory across all users in your organisation. Please reach out to your account manager if that is the case.
{% endhint %}


# Set up human spot checks

## Spot Check Sampling

Spot check sampling lets you route a configurable percentage of submissions for manual review before they are exported. This is useful during tenant onboarding when extraction quality needs to be validated, and can be gradually reduced as confidence grows.

### Configuration

Spot checking is configured per tenant in the organization settings. The configuration is a JSON object added to the tenant's `integrations` array.

#### Example

```json
{
  "type": "spot-check",
  "enabled": true,
  "sampleSize": 50,
  "spotCheckerEmails": [
    "reviewer@yourcompany.com",
    "triage-inbox@linear.app"
  ],
  "templateOverrides": [
    { "templateId": 3, "sampleSize": 50 }
  ]
}
```

### How it works

When a submission is fully processed and ready for export, the system rolls a random number against the configured sample rate. If selected:

1. The submission is **not exported** automatically
2. It stays in **"Ready for Spot Check"** status in the dashboard
3. All configured spot checkers receive a **notification email**
4. A reviewer opens the submission, assigns it to themselves, and reviews the extracted data
5. The reviewer clicks **Mark as completed** — this exports the submission, sets the status to **"Completed"**, and sends the end user a **confirmation email**
6. The end user receives a **confirmation email** that their submission was processed

Submissions that are not selected continue through the normal automatic export flow with no change.

#### Settings reference

| Setting                          | Type             | Required              | Description                                                                                                                                                        |
| -------------------------------- | ---------------- | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `enabled`                        | boolean          | Yes                   | Enables or disables spot checking for the tenant. Set to `false` to disable without removing the configuration.                                                    |
| `sampleSize`                     | integer (0-100)  | Yes                   | Percentage of submissions routed to spot check. `0` = no submissions selected (effectively disabled). `100` = every submission is selected.                        |
| `spotCheckerEmails`              | array of strings | Yes                   | Email addresses that receive a notification when a submission is selected. Supports multiple addresses. Can include team inboxes or Linear triage email addresses. |
| `templateOverrides`              | array of objects | No                    | Optional per-template overrides for the sample rate.                                                                                                               |
| `templateOverrides[].templateId` | integer          | Yes (within override) | The ID of the template to override.                                                                                                                                |
| `templateOverrides[].sampleSize` | integer (0-100)  | Yes (within override) | The sample rate for this specific template. Takes precedence over the tenant-level `sampleSize`.                                                                   |

#### Template overrides

You can set different sample rates for different templates. For example, a tenant might need 100% review on a new template while an established one only needs 10%:

```json
{
  "sampleSize": 10,
  "templateOverrides": [
    { "templateId": 7, "sampleSize": 100 }
  ]
}
```

In this example, template 7 has every submission reviewed, while all other templates are sampled at 10%.

### Enabling spot checking for a tenant

To enable spot checking for your tenant, contact the Nora team with:

* The **email addresses** that should receive spot check notifications
* The desired **sample size**
* Any **per-template overrides** if needed

> **Tip:** Start with `sampleSize: 100` during initial onboarding so every submission is reviewed. Reduce gradually as extraction quality is validated (e.g. 100 → 50 → 10 → 0).

### Disabling spot checking

Either set `enabled` to `false` or set `sampleSize` to `0`. Both stop new submissions from being selected, but existing spot check candidates in the queue are not affected.

### Reviewer workflow

#### 1. Receive notification

When a submission is selected for spot check, each email address in `spotCheckerEmails` receives an email with the submission name and number.

#### 2. Open the submission

In the FormFlow dashboard, spot-checked submissions are displayed with an orange **"Ready for Spot Check"** status badge, making them easy to identify in the submissions list.

#### 3. Assign and review

Open the submission and assign it to yourself. The status changes to **"In Spot Check"**. Review the extracted data against the source documents to verify accuracy.

#### 4. Complete the review

When satisfied with the data, click **Mark as completed**. This exports the submission, delivers a confirmation email to the end user, and sets the status to **"Completed"**.

### End user experience

The end user (the person who submitted the original documents) is not aware that their submission was selected for spot check. From their perspective:

* They submit documents as usual
* They receive the same confirmation email they would normally receive
* The only difference is a potential delay while the reviewer checks the data

### Recommended ramp-down schedule

| Phase               | sampleSize | When                                 |
| ------------------- | ---------- | ------------------------------------ |
| Initial onboarding  | 100        | First 1-2 weeks — review everything  |
| Building confidence | 50         | Weeks 2-4 — spot check half          |
| Monitoring          | 10-20      | Ongoing — periodic quality checks    |
| Fully automated     | 0          | When confident in extraction quality |

Adjust timelines based on submission volume and error rates observed during review.


# Processing limitations

In order to ensure reliable processing, Formflow enforces some limits on the types and sizes of files that it will process.&#x20;

These limitations remain the same, regardless of whether the interaction is happening via the web interface, over email, via a chat agent, or another channel.

### General limitations <a href="#file-attachment-limits" id="file-attachment-limits"></a>

* The size of any one file may not exceed 24MB.&#x20;
* only the following file types are allowed:&#x20;
  * pdf
  * doc, docx
  * csv
  * xls, xlsx
  * gif, png, jpg or jpeg, heic
* (if submitting over email) the submission must be sent at a valid target address

{% hint style="info" %}

#### Formflow will only process a *complete* submission

If, for example, one file out five does not conform to these limitations, then the entire submission will *not* be processed. This is to ensure that no data is omitted by accident.
{% endhint %}

### Restrictions on Excel files

Excel files are processed automatically, but as a workaround to excessively large workbooks, each worksheet will be truncated to 60 columns × 400 rows. If a worksheet in the Excel file exceeds these dimensions, the excess cells will be automatically trimmed. Only `.xlsx`, `.xls` files are supported.

### Feedback to errors

If any of the above problems are faced during processing, then the user will be informed via the UI, or they will receive a "failed" response email in reply. Whenever possible, they will be given a detailed error message, repeating much of the notes on this page.


# API documentation

Nora exposes a RESTful API, which uses standard HTTP verbs, response codes and authentication methods. Additionally, we use webhooks to notify about events. You should expect JSON-encoded response payloads.

### Getting started <a href="#getting-started" id="getting-started"></a>

To get started building an integration layer on top of Nora, you will need the following:

* A Nora tenant account, preferably a sandboxed demo account
* API authentication credentials, issued by Customer Support
* At least one template is defined in your account
* Some test files to submit

### Data flow <a href="#data-flow" id="data-flow"></a>

The basic data flow is as follows:

1. **Submit:** the end user submits some files for processing via the web UI, over email or via API. They also specify a Template.
2. **Process:** Nora creates a Submission and processes the files therein. A Submission in `processing` status is made available over the API.
3. **Validate:** once a Submission is processed, the end user reviews the extraction results for accuracy and completeness. They may also add additional details manually. Once happy, they will mark the submission as `completed`.
4. **Consume:** third party integrations poll Nora for new Submissions, and if a new `completed` submission is found, process it according to internal business logic.


# Authentication and limits

To interact with the Nora API, you need to authenticate your requests using a token. All API requests are made to `https://main.formflow-dev.net`. All requests should be made using HTTPS.

### Obtain credentials <a href="#obtain-credentials" id="obtain-credentials"></a>

To access the Nora API, you will need a `clientId` and `clientSecret` value, which are provided by Nora Customer Support upon request. You will also need to know your `organizationId` value.

{% hint style="info" %}

#### Keep your credentials secure

Ensure your credentials (clientId, clientSecret, and organizationId) are kept secure. Do not expose them in client-side code or public repositories. Should a secret value or its hash surface in a public leaks database, Nora will pre-emptively deactivate such credentials.
{% endhint %}

You can exchange these for a `Bearer` token, valid for 3600 seconds (1 hour):

**Endpoint**

[`POST /api/token`](broken://pages/d52e928cc03ab5f837a429392ee63d6a50d18721#post-token)

**Headers**

`Accept: application/jsonContent-Type: application/json`

**Example request body**

```json
{
  "clientId": "2mvc4Ev4qpU8Y0FbADRq4xpZOhf",
  "clientSecret": "{zPYnGM$c&p+NLH0JNm-xdD'6+VHVuE`1R/xe(7+*eSAIOW^C~)M:8H_32Vd#{J",
  "organizationId": "2nHy8pbeoD5CM9WbjvyGj1VKoUu"
}
```

**Example 200 response**

```json
{
  "token": "eyJhbGciOiJSUzI1NiIs...Ce7EdUsAw",
  "expires_in": 3600,
  "token_type": "Bearer"
}
```

### Authenticate your requests <a href="#authenticate-your-requests" id="authenticate-your-requests"></a>

You can use the token in the response to authenticate all subsequent requests by including it in the Authorization header: `Authorization: Bearer <your_token>`

### Refreshing the token <a href="#refreshing-the-token" id="refreshing-the-token"></a>

Since the token is valid for 3600 seconds (1 hour), you will need to repeat the token request process to refresh your token. Ensure your application is designed to handle token expiration by checking against the `expires_in` value in the response.

### Rate Limiting <a href="#rate-limiting" id="rate-limiting"></a>

The API enforces a rate limit of 60 requests per minute to ensure fair usage and maintain performance. If you exceed this limit, the API will return a `429 Too Many Requests` response code. When this occurs, you must wait until the rate limit resets before making additional requests. To avoid interruptions, ensure your application adheres to the rate limit policy.


# API Client Management

Manage API clients and credentials

## GET /api-clients

>

```json
{"openapi":"3.0.0","info":{"title":"Formflow API","version":"1.0"},"tags":[{"name":"API Client Management","description":"Manage API clients and credentials"}],"security":[{"access-token":[]}],"components":{"securitySchemes":{},"schemas":{"ListApiClientsResponseDto":{"type":"object","properties":{"clients":{"type":"array","items":{"$ref":"#/components/schemas/ApiClient"}}},"required":["clients"]},"ApiClient":{"type":"object","properties":{}}}},"paths":{"/api-clients":{"get":{"operationId":"ApiClientsController_listApiClients","parameters":[],"responses":{"200":{"description":"API clients retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListApiClientsResponseDto"}}}}},"tags":["API Client Management"]}}}}
```

## POST /api-clients

>

```json
{"openapi":"3.0.0","info":{"title":"Formflow API","version":"1.0"},"tags":[{"name":"API Client Management","description":"Manage API clients and credentials"}],"security":[{"access-token":[]}],"components":{"securitySchemes":{},"schemas":{"CreateApiClientsRequestDto":{"type":"object","properties":{}},"CreateApiClientsResponseDto":{"type":"object","properties":{"clientSecret":{"type":"string","description":"The raw client secret (only shown once during creation)"},"message":{"type":"string","description":"Important message about storing the secret"}},"required":["clientSecret","message"]}}},"paths":{"/api-clients":{"post":{"operationId":"ApiClientsController_createApiClient","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateApiClientsRequestDto"}}}},"responses":{"201":{"description":"API client created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateApiClientsResponseDto"}}}}},"tags":["API Client Management"]}}}}
```

## DELETE /api-clients/{clientId}

>

```json
{"openapi":"3.0.0","info":{"title":"Formflow API","version":"1.0"},"tags":[{"name":"API Client Management","description":"Manage API clients and credentials"}],"security":[{"access-token":[]}],"components":{"securitySchemes":{},"schemas":{"RevokeApiClientResponseDto":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"]}}},"paths":{"/api-clients/{clientId}":{"delete":{"operationId":"ApiClientsController_revokeApiClient","parameters":[{"name":"clientId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"API client revoked successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RevokeApiClientResponseDto"}}}}},"tags":["API Client Management"]}}}}
```

## PATCH /api-clients/{clientId}/rotate-secret

>

```json
{"openapi":"3.0.0","info":{"title":"Formflow API","version":"1.0"},"tags":[{"name":"API Client Management","description":"Manage API clients and credentials"}],"security":[{"access-token":[]}],"components":{"securitySchemes":{},"schemas":{"RotateSecretResponseDto":{"type":"object","properties":{"clientId":{"type":"string"},"clientSecret":{"type":"string"},"message":{"type":"string"}},"required":["clientId","clientSecret","message"]}}},"paths":{"/api-clients/{clientId}/rotate-secret":{"patch":{"operationId":"ApiClientsController_rotateSecret","parameters":[{"name":"clientId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Secret rotated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RotateSecretResponseDto"}}}}},"tags":["API Client Management"]}}}}
```


# API Token

Token generation and management

## Get API access token

> Exchange API client credentials for an access token. Use this token in the Authorization header as "Bearer \<token>" for authenticated API requests.

```json
{"openapi":"3.0.0","info":{"title":"Formflow API","version":"1.0"},"tags":[{"name":"API Token","description":"Token generation and management"}],"paths":{"/api/token":{"post":{"description":"Exchange API client credentials for an access token. Use this token in the Authorization header as \"Bearer <token>\" for authenticated API requests.","operationId":"TokenController_getToken","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiClientsRequestDto"}}}},"responses":{"200":{"description":"Successfully authenticated and returned access token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetTokenResponseDto"}}}},"400":{"description":"Bad request - missing or invalid parameters"},"401":{"description":"Invalid credentials or unauthorized"}},"summary":"Get API access token","tags":["API Token"]}}},"components":{"schemas":{"ApiClientsRequestDto":{"type":"object","properties":{"clientId":{"type":"string","description":"The API client ID provided when the API client was created"},"clientSecret":{"type":"string","description":"The API client secret provided when the API client was created"},"organizationId":{"type":"string","description":"The organization ID associated with this API client"}},"required":["clientId","clientSecret","organizationId"]},"GetTokenResponseDto":{"type":"object","properties":{"token":{"type":"string","description":"JWT access token to use for authenticated API requests"},"token_type":{"type":"string","description":"Type of token (always \"Bearer\")"},"expires_in":{"type":"number","description":"Token expiration time in seconds"}},"required":["token","token_type","expires_in"]}}}}
```


# Submissions

Operations related to form submissions

## GET /api/submission

> Get submissions list

```json
{"openapi":"3.0.0","info":{"title":"Formflow API","version":"1.0"},"tags":[{"name":"Submissions","description":"Operations related to form submissions"}],"security":[{"access-token":[]}],"components":{"securitySchemes":{}},"paths":{"/api/submission":{"get":{"operationId":"SubmissionController_getAllSubmissions","parameters":[{"name":"page","required":false,"in":"query","description":"Page number (default: 1)","schema":{"type":"number"}},{"name":"perPage","required":false,"in":"query","description":"Items per page (default: 10)","schema":{"type":"number"}},{"name":"sort","required":false,"in":"query","description":"Sort field (default: createdAt)","schema":{"enum":["createdAt","updatedAt"],"type":"string"}},{"name":"sortDir","required":false,"in":"query","description":"Sort direction (default: desc)","schema":{"enum":["asc","desc"],"type":"string"}},{"name":"status","required":false,"in":"query","description":"Comma-separated list of statuses to filter by","schema":{"type":"string"}}],"responses":{"200":{"description":"Returns a paginated list of submissions"}},"summary":"Get submissions list","tags":["Submissions"]}}}}
```

## POST /api/submission

> Create a new submission

```json
{"openapi":"3.0.0","info":{"title":"Formflow API","version":"1.0"},"tags":[{"name":"Submissions","description":"Operations related to form submissions"}],"security":[{"access-token":[]}],"components":{"securitySchemes":{},"schemas":{"CreateSubmissionDto":{"type":"object","properties":{"id":{"type":"string","description":"Unique identifier for the submission"},"name":{"type":"string","description":"Submission name"},"templateId":{"type":"number","description":"ID of the associated template"},"uploaderUserId":{"type":"string","description":"ID of the user who uploaded the submission"},"userId":{"type":"string","description":"ID of the user associated with the submission"},"emailSender":{"type":"string","description":"Email address of the sender"},"emailBody":{"type":"string","description":"Email body or additional context"},"emailMessageId":{"type":"string","description":"Email message ID"},"payload":{"type":"object","description":"Submission payload data"},"manualPayload":{"type":"object","description":"Manual payload data"},"metaPayload":{"type":"object","description":"Metadata payload"}}},"SubmissionDto":{"type":"object","properties":{"id":{"type":"string","description":"Unique identifier for the submission"},"submissionNumber":{"type":"number","description":"Submission sequence number"},"name":{"type":"string","description":"Submission name"},"template":{"description":"Associated template","allOf":[{"$ref":"#/components/schemas/Template"}]},"templateId":{"type":"number","description":"ID of the associated template"},"uploaderUserId":{"type":"string","description":"ID of the user who uploaded the submission"},"userId":{"type":"string","description":"ID of the user associated with the submission"},"files":{"description":"Files attached to the submission","type":"array","items":{"type":"string"}},"status":{"description":"Current status of the submission","allOf":[{"$ref":"#/components/schemas/SubmissionStatus"}]},"emailSender":{"type":"string","description":"Email address of the sender"},"emailBody":{"type":"string","description":"Email body or additional context"},"emailMessageId":{"type":"string","description":"Email message ID"},"payload":{"type":"object","description":"Submission payload data"},"manualPayload":{"type":"object","description":"Manual payload data"},"metaPayload":{"type":"object","description":"Metadata payload"},"OBEExtraction":{"type":"string","description":"ID of the old backend extraction event"},"NBEExtraction":{"type":"string","description":"ID of the new backend extraction event"},"createdAt":{"format":"date-time","type":"string","description":"Timestamp when the submission was created"},"updatedAt":{"format":"date-time","type":"string","description":"Timestamp when the submission was last updated"}},"required":["id","submissionNumber","name","template","templateId","files","status","createdAt","updatedAt"]},"Template":{"type":"object","properties":{"id":{"type":"number","description":"The ID of the template"},"templateId":{"type":"string","description":"The UUID of the template"},"name":{"type":"string","description":"The name of the template"},"description":{"type":"string","description":"The description of the template"},"version":{"type":"number","description":"The version of the template"},"schema":{"type":"object","description":"The schema of the template"},"vendorSchemaName":{"type":"string","description":"The vendor schema name"},"vendorSchemaVersion":{"type":"number","description":"The vendor schema version"},"vendorSchema":{"type":"object","description":"The vendor schema"},"dereferencedVendorSchema":{"type":"object","description":"The dereferenced vendor schema with resolved $ref references"},"aiGenerated":{"type":"boolean","description":"Whether the template was generated using AI"},"emailAlias":{"type":"string","description":"The email alias"},"extractionStrategyId":{"type":"string","description":"The uuid of extraction strategy template uses."},"extractionStrategy":{"description":"Associated extraction strategy","allOf":[{"$ref":"#/components/schemas/Strategy"}]},"referenceStrategy":{"description":"Associated reference strategy","allOf":[{"$ref":"#/components/schemas/Strategy"}]},"state":{"type":"string","description":"The state of the template","enum":["draft","published"],"default":"published"},"parsingInstructions":{"type":"object","description":"the parsing instructions for a template"}},"required":["id","templateId","name","version","aiGenerated","extractionStrategyId","extractionStrategy","referenceStrategy","state","parsingInstructions"]},"Strategy":{"type":"object","properties":{"id":{"type":"string","description":"Unique identifier for the strategy"},"identifier":{"type":"string","description":"Identifier of the strategy type","enum":["extraction-claude-3-7","extraction-gemini-2-5-loose","extraction-claude-3-7-thinking","extraction-atomic"]},"type":{"type":"string","description":"Type of the strategy","default":"extraction"},"displayName":{"type":"string","description":"Display name of the strategy"},"description":{"type":"string","description":"Description of the strategy"},"createdAt":{"format":"date-time","type":"string","description":"Timestamp when the strategy was created"},"updatedAt":{"format":"date-time","type":"string","description":"Timestamp when the strategy was last updated"}},"required":["id","identifier","type","displayName","createdAt","updatedAt"]},"SubmissionStatus":{"type":"string","enum":["created","processing","processing-failed","processed","in-review","completed","exported","export-failed","discarded","preparing","queued"]}}},"paths":{"/api/submission":{"post":{"operationId":"SubmissionController_createSubmission","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSubmissionDto"}}}},"responses":{"201":{"description":"The submission has been successfully created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubmissionDto"}}}}},"summary":"Create a new submission","tags":["Submissions"]}}}}
```

## GET /api/submission/{id}

> Get submission

```json
{"openapi":"3.0.0","info":{"title":"Formflow API","version":"1.0"},"tags":[{"name":"Submissions","description":"Operations related to form submissions"}],"security":[{"access-token":[]}],"components":{"securitySchemes":{}},"paths":{"/api/submission/{id}":{"get":{"operationId":"SubmissionController_getSubmissionById","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"summary":"Get submission","tags":["Submissions"]}}}}
```

## PATCH /api/submission/{id}

> Update submission

```json
{"openapi":"3.0.0","info":{"title":"Formflow API","version":"1.0"},"tags":[{"name":"Submissions","description":"Operations related to form submissions"}],"security":[{"access-token":[]}],"components":{"securitySchemes":{},"schemas":{"UpdateSubmissionDto":{"type":"object","properties":{"id":{"type":"string","description":"Unique identifier for the submission"},"submissionNumber":{"type":"number","description":"Submission sequence number"},"name":{"type":"string","description":"Submission name"},"templateId":{"type":"number","description":"ID of the associated template"},"uploaderUserId":{"type":"string","description":"ID of the user who uploaded the submission"},"userId":{"type":"string","description":"ID of the user associated with the submission"},"status":{"description":"Current status of the submission","allOf":[{"$ref":"#/components/schemas/SubmissionStatus"}]},"emailSender":{"type":"string","description":"Email address of the sender"},"emailBody":{"type":"string","description":"Email body or additional context"},"emailMessageId":{"type":"string","description":"Email message ID"},"payload":{"type":"object","description":"Submission payload data"},"manualPayload":{"type":"object","description":"Manual payload data"},"metaPayload":{"type":"object","description":"Metadata payload"},"createdAt":{"format":"date-time","type":"string","description":"Timestamp when the submission was created"},"updatedAt":{"format":"date-time","type":"string","description":"Timestamp when the submission was last updated"}}},"SubmissionStatus":{"type":"string","enum":["created","processing","processing-failed","processed","in-review","completed","exported","export-failed","discarded","preparing","queued"]}}},"paths":{"/api/submission/{id}":{"patch":{"operationId":"SubmissionController_updateSubmission","parameters":[{"name":"id","required":true,"in":"path","description":"Submission ID","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateSubmissionDto"}}}},"responses":{"200":{"description":"The submission has been successfully updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateSubmissionDto"}}}}},"summary":"Update submission","tags":["Submissions"]}}}}
```

## GET /api/submission/{id}/references

> Get references for submission

```json
{"openapi":"3.0.0","info":{"title":"Formflow API","version":"1.0"},"tags":[{"name":"Submissions","description":"Operations related to form submissions"}],"security":[{"access-token":[]}],"components":{"securitySchemes":{}},"paths":{"/api/submission/{id}/references":{"get":{"operationId":"SubmissionController_getSubmissionReferences","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Returns references for a specific submission"}},"summary":"Get references for submission","tags":["Submissions"]}}}}
```

## GET /api/submission/{id}/events

> Get all events for a submission

```json
{"openapi":"3.0.0","info":{"title":"Formflow API","version":"1.0"},"tags":[{"name":"Submissions","description":"Operations related to form submissions"}],"security":[{"access-token":[]}],"components":{"securitySchemes":{},"schemas":{"Event":{"type":"object","properties":{"id":{"type":"string","description":"Unique identifier for the event"},"submissionId":{"type":"string","description":"Related submission ID","nullable":true},"type":{"type":"string","description":"Type of event","enum":["upload","email","conversion","extraction","export","discard","reference","generate-schema","metadata"]},"state":{"type":"string","description":"Current state of the event","enum":["pending","succeed","fail"]},"createdAt":{"format":"date-time","type":"string","description":"Timestamp when the event was created"},"updatedAt":{"format":"date-time","type":"string","description":"Timestamp when the event was last updated"}},"required":["id","submissionId","type","state","createdAt","updatedAt"]}}},"paths":{"/api/submission/{id}/events":{"get":{"operationId":"SubmissionController_getSubmissionEvents","parameters":[{"name":"id","required":true,"in":"path","description":"Submission ID to retrieve events for","schema":{"type":"string"}}],"responses":{"200":{"description":"Returns all events and their current status for the specified submission","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Event"}}}}},"404":{"description":"Submission with ID 123e4567-e89b-12d3-a456-426614174001 not found"}},"summary":"Get all events for a submission","tags":["Submissions"]}}}}
```

## POST /api/submission/{id}/upload

> Get temporary S3 URL for client-side upload

```json
{"openapi":"3.0.0","info":{"title":"Formflow API","version":"1.0"},"tags":[{"name":"Submissions","description":"Operations related to form submissions"}],"security":[{"access-token":[]}],"components":{"securitySchemes":{},"schemas":{"UploadRequestDto":{"type":"object","properties":{"fileName":{"type":"string","description":"Name of the file to upload"},"contentType":{"type":"string","description":"MIME type of the file"}},"required":["fileName","contentType"]}}},"paths":{"/api/submission/{id}/upload":{"post":{"operationId":"SubmissionController_getUploadUrl","parameters":[{"name":"id","required":true,"in":"path","description":"Submission ID","schema":{"type":"string"}}],"requestBody":{"required":true,"description":"Upload file details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UploadRequestDto"}}}},"responses":{"201":{"description":""}},"summary":"Get temporary S3 URL for client-side upload","tags":["Submissions"]}}}}
```

## POST /api/submission/{id}/export

> Export submission to ledger

```json
{"openapi":"3.0.0","info":{"title":"Formflow API","version":"1.0"},"tags":[{"name":"Submissions","description":"Operations related to form submissions"}],"security":[{"access-token":[]}],"components":{"securitySchemes":{}},"paths":{"/api/submission/{id}/export":{"post":{"operationId":"SubmissionController_exportSubmission","parameters":[{"name":"id","required":true,"in":"path","description":"Submission ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Submission successfully exported"},"404":{"description":"Submission not found"},"500":{"description":"Export failed"}},"summary":"Export submission to ledger","tags":["Submissions"]}}}}
```


# Templates

Operations related to form templates

## GET /api/template

>

```json
{"openapi":"3.0.0","info":{"title":"Formflow API","version":"1.0"},"tags":[{"name":"Templates","description":"Operations related to form templates"}],"security":[{"access-token":[]}],"components":{"securitySchemes":{}},"paths":{"/api/template":{"get":{"operationId":"TemplateController_getAllTemplates","parameters":[{"name":"page","required":false,"in":"query","description":"Page number (default: 1)","schema":{"type":"number"}},{"name":"perPage","required":false,"in":"query","description":"Items per page (default: 100)","schema":{"type":"number"}}],"responses":{"200":{"description":"Returns a paginated list of templates"}},"tags":["Templates"]}}}}
```

## POST /api/template

>

```json
{"openapi":"3.0.0","info":{"title":"Formflow API","version":"1.0"},"tags":[{"name":"Templates","description":"Operations related to form templates"}],"security":[{"access-token":[]}],"components":{"securitySchemes":{},"schemas":{"CreateTemplateDto":{"type":"object","properties":{"name":{"type":"string","description":"The name of the template"},"description":{"type":"string","description":"The description of the template"},"version":{"type":"number","description":"The version of the template"},"schema":{"type":"object","description":"The schema of the template"},"aiGenerated":{"type":"boolean","description":"Wheter the template was generated using AI"},"vendorSchemaName":{"type":"string","description":"The vendor schema name"},"vendorSchemaVersion":{"type":"number","description":"The vendor schema version"},"vendorSchema":{"type":"object","description":"The vendor schema"},"dereferencedVendorSchema":{"type":"object","description":"The dereferenced vendor schema with resolved $ref references"},"emailAlias":{"type":"string","description":"The email alias"},"state":{"type":"string","description":"The state of the template","enum":["draft","published"],"default":"published"}},"required":["name","version"]}}},"paths":{"/api/template":{"post":{"operationId":"TemplateController_createTemplate","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateTemplateDto"}}}},"responses":{"201":{"description":"Creates a new template"}},"tags":["Templates"]}}}}
```

## GET /api/template/{id}

>

```json
{"openapi":"3.0.0","info":{"title":"Formflow API","version":"1.0"},"tags":[{"name":"Templates","description":"Operations related to form templates"}],"security":[{"access-token":[]}],"components":{"securitySchemes":{}},"paths":{"/api/template/{id}":{"get":{"operationId":"TemplateController_getTemplateById","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"number"}}],"responses":{"200":{"description":""}},"tags":["Templates"]}}}}
```

## PUT /api/template/{id}

>

```json
{"openapi":"3.0.0","info":{"title":"Formflow API","version":"1.0"},"tags":[{"name":"Templates","description":"Operations related to form templates"}],"security":[{"access-token":[]}],"components":{"securitySchemes":{},"schemas":{"UpdateTemplateDto":{"type":"object","properties":{"name":{"type":"string","description":"The name of the template"},"description":{"type":"string","description":"The description of the template"},"version":{"type":"number","description":"The version of the template"},"schema":{"type":"object","description":"The schema of the template"},"vendorSchemaName":{"type":"string","description":"The vendor schema name"},"vendorSchemaVersion":{"type":"number","description":"The vendor schema version"},"vendorSchema":{"type":"object","description":"The vendor schema"},"dereferencedVendorSchema":{"type":"object","description":"The dereferenced vendor schema with resolved $ref references"},"emailAlias":{"type":"string","description":"The email alias"},"extractionStrategyId":{"type":"string","description":"The uuid of extraction strategy template uses."},"state":{"type":"string","description":"The state of the template","enum":["draft","published"],"default":"published"},"parsingInstructions":{"type":"object","description":"the parsing instructions for a template"}}}}},"paths":{"/api/template/{id}":{"put":{"operationId":"TemplateController_updateTemplate","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"number"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateTemplateDto"}}}},"responses":{"200":{"description":"Updates an existing template"},"413":{"description":"Payload too large"}},"tags":["Templates"]}}}}
```

## DELETE /api/template/{id}

>

```json
{"openapi":"3.0.0","info":{"title":"Formflow API","version":"1.0"},"tags":[{"name":"Templates","description":"Operations related to form templates"}],"security":[{"access-token":[]}],"components":{"securitySchemes":{}},"paths":{"/api/template/{id}":{"delete":{"operationId":"TemplateController_deleteTemplate","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"number"}}],"responses":{"204":{"description":"Soft deletes an existing template"}},"tags":["Templates"]}}}}
```

## PATCH /api/template/{id}

> Update template

```json
{"openapi":"3.0.0","info":{"title":"Formflow API","version":"1.0"},"tags":[{"name":"Templates","description":"Operations related to form templates"}],"security":[{"access-token":[]}],"components":{"securitySchemes":{},"schemas":{"UpdateTemplateDto":{"type":"object","properties":{"name":{"type":"string","description":"The name of the template"},"description":{"type":"string","description":"The description of the template"},"version":{"type":"number","description":"The version of the template"},"schema":{"type":"object","description":"The schema of the template"},"vendorSchemaName":{"type":"string","description":"The vendor schema name"},"vendorSchemaVersion":{"type":"number","description":"The vendor schema version"},"vendorSchema":{"type":"object","description":"The vendor schema"},"dereferencedVendorSchema":{"type":"object","description":"The dereferenced vendor schema with resolved $ref references"},"emailAlias":{"type":"string","description":"The email alias"},"extractionStrategyId":{"type":"string","description":"The uuid of extraction strategy template uses."},"state":{"type":"string","description":"The state of the template","enum":["draft","published"],"default":"published"},"parsingInstructions":{"type":"object","description":"the parsing instructions for a template"}}}}},"paths":{"/api/template/{id}":{"patch":{"operationId":"TemplateController_patchTemplate","parameters":[{"name":"id","required":true,"in":"path","description":"Template ID","schema":{"type":"number"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateTemplateDto"}}}},"responses":{"200":{"description":"The template has been successfully updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateTemplateDto"}}}}},"summary":"Update template","tags":["Templates"]}}}}
```

## GET /api/template/{id}/submissions

>

```json
{"openapi":"3.0.0","info":{"title":"Formflow API","version":"1.0"},"tags":[{"name":"Templates","description":"Operations related to form templates"}],"security":[{"access-token":[]}],"components":{"securitySchemes":{}},"paths":{"/api/template/{id}/submissions":{"get":{"operationId":"TemplateController_getTemplateSubmissions","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"number"}},{"name":"page","required":false,"in":"query","description":"Page number (default: 1)","schema":{"type":"number"}},{"name":"perPage","required":false,"in":"query","description":"Items per page (default: 10)","schema":{"type":"number"}}],"responses":{"200":{"description":"Returns a paginated list of submissions for the specified template"}},"tags":["Templates"]}}}}
```

## POST /api/template/import-from-ledger

> Import schema from Ledger

```json
{"openapi":"3.0.0","info":{"title":"Formflow API","version":"1.0"},"tags":[{"name":"Templates","description":"Operations related to form templates"}],"security":[{"access-token":[]}],"components":{"securitySchemes":{},"schemas":{"ImportFromLedgerDto":{"type":"object","properties":{"schema":{"type":"object","description":"The updated schema from Ledger"},"vendorSchemaName":{"type":"string","description":"The vendor schema name"},"vendor_schema_name":{"type":"string","description":"The vendor schema name (alternative)"},"vendorSchemaVersion":{"type":"number","description":"The vendor schema version"},"vendor_schema_version":{"type":"number","description":"The vendor schema version (alternative)"},"vendorSchema":{"type":"object","description":"The vendor schema"},"vendor_schema":{"type":"object","description":"The vendor schema (alternative snake_case)"},"dereferencedVendorSchema":{"type":"object","description":"The dereferenced vendor schema with resolved $ref references"}}}}},"paths":{"/api/template/import-from-ledger":{"post":{"operationId":"TemplateController_importSchemaFromLedger","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImportFromLedgerDto"}}}},"responses":{"201":{"description":"transforms Ledger schema into a formflow template"}},"summary":"Import schema from Ledger","tags":["Templates"]}}}}
```

## POST /api/template/{id}/duplicate

> Duplicate an existing template

```json
{"openapi":"3.0.0","info":{"title":"Formflow API","version":"1.0"},"tags":[{"name":"Templates","description":"Operations related to form templates"}],"security":[{"access-token":[]}],"components":{"securitySchemes":{}},"paths":{"/api/template/{id}/duplicate":{"post":{"operationId":"TemplateController_duplicateTemplate","parameters":[{"name":"id","required":true,"in":"path","description":"Template ID to duplicate","schema":{"type":"number"}}],"responses":{"201":{"description":"Creates a duplicate of the existing template with all AI hints preserved"}},"summary":"Duplicate an existing template","tags":["Templates"]}}}}
```

## POST /api/template/{id}/update-from-ledger

> Import updated schema from Ledger

```json
{"openapi":"3.0.0","info":{"title":"Formflow API","version":"1.0"},"tags":[{"name":"Templates","description":"Operations related to form templates"}],"security":[{"access-token":[]}],"components":{"securitySchemes":{},"schemas":{"ImportFromLedgerDto":{"type":"object","properties":{"schema":{"type":"object","description":"The updated schema from Ledger"},"vendorSchemaName":{"type":"string","description":"The vendor schema name"},"vendor_schema_name":{"type":"string","description":"The vendor schema name (alternative)"},"vendorSchemaVersion":{"type":"number","description":"The vendor schema version"},"vendor_schema_version":{"type":"number","description":"The vendor schema version (alternative)"},"vendorSchema":{"type":"object","description":"The vendor schema"},"vendor_schema":{"type":"object","description":"The vendor schema (alternative snake_case)"},"dereferencedVendorSchema":{"type":"object","description":"The dereferenced vendor schema with resolved $ref references"}}}}},"paths":{"/api/template/{id}/update-from-ledger":{"post":{"operationId":"TemplateController_importFromLedger","parameters":[{"name":"id","required":true,"in":"path","description":"Template ID to update from Ledger","schema":{"type":"number"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImportFromLedgerDto"}}}},"responses":{"201":{"description":"Creates a new template with updated schema from Ledger while preserving AI hints"}},"summary":"Import updated schema from Ledger","tags":["Templates"]}}}}
```


# Processing

Operations for processing submissions

## Start processing

> Use this endpoint to start processing a new submission. Provide the submission data in the request body.

```json
{"openapi":"3.0.0","info":{"title":"Formflow API","version":"1.0"},"tags":[{"name":"Processing","description":"Operations for processing submissions"}],"security":[{"access-token":[]}],"components":{"securitySchemes":{},"schemas":{"SubmissionEntryDTO":{"type":"object","properties":{"submissionId":{"type":"string","description":"The ID of the submission"},"generateTemplate":{"type":"boolean","description":"Whether to generate a template for the submission","default":false}},"required":["submissionId"]},"SubmissionResponseDto":{"type":"object","properties":{"submissionId":{"type":"string","description":"The ID of the submission"},"message":{"type":"string","description":"Response message"},"templateId":{"type":"number","description":"The ID of the template if generated"}},"required":["submissionId","message"]}}},"paths":{"/api/processing":{"post":{"description":"Use this endpoint to start processing a new submission. Provide the submission data in the request body.","operationId":"ProcessingController_startProcessing","parameters":[],"requestBody":{"required":true,"description":"The submission data to be processed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubmissionEntryDTO"}}}},"responses":{"200":{"description":"Returns a message indicating that the processing service has started.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubmissionResponseDto"}}}}},"summary":"Start processing","tags":["Processing"]}}}}
```


# Files

File management operations

## GET /api/file/{id}

> Get file by ID

```json
{"openapi":"3.0.0","info":{"title":"Formflow API","version":"1.0"},"tags":[{"name":"Files","description":"File management operations"}],"security":[{"access-token":[]}],"components":{"securitySchemes":{},"schemas":{"FileDto":{"type":"object","properties":{"id":{"type":"string","description":"Unique identifier for the file"},"name":{"type":"string","description":"Name of the file"},"fileName":{"type":"string","description":"Original file name"},"fileType":{"type":"string","description":"Type of the file (e.g., MIME type)"},"fileSize":{"type":"number","description":"Size of the file in bytes"},"url":{"type":"string","description":"Public URL of the file"},"createdAt":{"format":"date-time","type":"string","description":"Timestamp when the file was created"},"updatedAt":{"format":"date-time","type":"string","description":"Timestamp when the file was last updated"}},"required":["id","fileName","fileType","fileSize","url","createdAt","updatedAt"]}}},"paths":{"/api/file/{id}":{"get":{"operationId":"FileController_getFileById","parameters":[{"name":"id","required":true,"in":"path","description":"File ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Returns the file details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FileDto"}}}},"404":{"description":"File not found"}},"summary":"Get file by ID","tags":["Files"]}}}}
```

## DELETE /api/file/{id}

> Delete a file

```json
{"openapi":"3.0.0","info":{"title":"Formflow API","version":"1.0"},"tags":[{"name":"Files","description":"File management operations"}],"security":[{"access-token":[]}],"components":{"securitySchemes":{}},"paths":{"/api/file/{id}":{"delete":{"operationId":"FileController_deleteFile","parameters":[{"name":"id","required":true,"in":"path","description":"File ID","schema":{"type":"string"}}],"responses":{"204":{"description":"The file has been successfully deleted"},"404":{"description":"File not found"}},"summary":"Delete a file","tags":["Files"]}}}}
```

## GET /api/file/{id}/view

> View/download file directly

```json
{"openapi":"3.0.0","info":{"title":"Formflow API","version":"1.0"},"tags":[{"name":"Files","description":"File management operations"}],"security":[{"access-token":[]}],"components":{"securitySchemes":{}},"paths":{"/api/file/{id}/view":{"get":{"operationId":"FileController_viewFile","parameters":[{"name":"id","required":true,"in":"path","description":"File ID","schema":{"type":"string"}}],"responses":{"302":{"description":"Redirects to file download"},"404":{"description":"File not found"}},"summary":"View/download file directly","tags":["Files"]}}}}
```


# Webhooks

Webhook management and events

## GET /api/webhook/events

> Get all available webhook event types

```json
{"openapi":"3.0.0","info":{"title":"Formflow API","version":"1.0"},"tags":[{"name":"Webhooks","description":"Webhook management and events"}],"security":[{"access-token":[]}],"components":{"securitySchemes":{},"schemas":{"WebhookEventInfoDto":{"type":"object","properties":{"eventType":{"type":"string","description":"Event type identifier"},"description":{"type":"string","description":"Human-readable description of when this event is triggered"},"examplePayload":{"type":"object","description":"Example payload structure for this event type"}},"required":["eventType","description","examplePayload"]}}},"paths":{"/api/webhook/events":{"get":{"operationId":"WebhookController_getAvailableEvents","parameters":[],"responses":{"200":{"description":"Returns list of all webhook event types with descriptions and example payloads","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/WebhookEventInfoDto"}}}}}},"summary":"Get all available webhook event types","tags":["Webhooks"]}}}}
```

## POST /api/webhook/subscription

> Create webhook subscription

```json
{"openapi":"3.0.0","info":{"title":"Formflow API","version":"1.0"},"tags":[{"name":"Webhooks","description":"Webhook management and events"}],"security":[{"access-token":[]}],"components":{"securitySchemes":{},"schemas":{"WebhookSubscriberCreateDto":{"type":"object","properties":{"event":{"description":"Event type that triggers the webhook","allOf":[{"$ref":"#/components/schemas/WebhookEventType"}]},"url":{"type":"string","description":"URL where webhook notifications will be sent"}},"required":["event","url"]},"WebhookEventType":{"type":"string","enum":["extracted","export","validation-success"]},"WebhookSubscriber":{"type":"object","properties":{"id":{"type":"string","description":"Unique identifier of the webhook subscriber"},"event":{"description":"Event type that triggers the webhook","allOf":[{"$ref":"#/components/schemas/WebhookEventType"}]},"url":{"type":"string","description":"URL where webhook notifications will be sent"},"secret":{"type":"object","description":"Secret token used for HMAC signature generation"},"createdAt":{"format":"date-time","type":"string","description":"Timestamp when the webhook subscription was created"},"updatedAt":{"format":"date-time","type":"string","description":"Timestamp when the webhook subscription was last updated"}},"required":["id","event","url","secret","createdAt","updatedAt"]}}},"paths":{"/api/webhook/subscription":{"post":{"operationId":"WebhookController_createSubscription","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookSubscriberCreateDto"}}}},"responses":{"201":{"description":"The webhook subscription has been created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookSubscriber"}}}},"400":{"description":"Bad request - URL and event are required"},"500":{"description":"Internal server error"}},"summary":"Create webhook subscription","tags":["Webhooks"]}}}}
```

## GET /api/webhook/subscriptions

> Get all webhook subscriptions

```json
{"openapi":"3.0.0","info":{"title":"Formflow API","version":"1.0"},"tags":[{"name":"Webhooks","description":"Webhook management and events"}],"security":[{"access-token":[]}],"components":{"securitySchemes":{},"schemas":{"WebhookSubscriber":{"type":"object","properties":{"id":{"type":"string","description":"Unique identifier of the webhook subscriber"},"event":{"description":"Event type that triggers the webhook","allOf":[{"$ref":"#/components/schemas/WebhookEventType"}]},"url":{"type":"string","description":"URL where webhook notifications will be sent"},"secret":{"type":"object","description":"Secret token used for HMAC signature generation"},"createdAt":{"format":"date-time","type":"string","description":"Timestamp when the webhook subscription was created"},"updatedAt":{"format":"date-time","type":"string","description":"Timestamp when the webhook subscription was last updated"}},"required":["id","event","url","secret","createdAt","updatedAt"]},"WebhookEventType":{"type":"string","enum":["extracted","export","validation-success"]}}},"paths":{"/api/webhook/subscriptions":{"get":{"operationId":"WebhookController_getAllSubscriptions","parameters":[],"responses":{"200":{"description":"List of webhook subscriptions","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/WebhookSubscriber"}}}}},"500":{"description":"Internal server error"}},"summary":"Get all webhook subscriptions","tags":["Webhooks"]}}}}
```

## GET /api/webhook/subscription/{id}

> Get webhook subscription by ID

```json
{"openapi":"3.0.0","info":{"title":"Formflow API","version":"1.0"},"tags":[{"name":"Webhooks","description":"Webhook management and events"}],"security":[{"access-token":[]}],"components":{"securitySchemes":{},"schemas":{"WebhookSubscriber":{"type":"object","properties":{"id":{"type":"string","description":"Unique identifier of the webhook subscriber"},"event":{"description":"Event type that triggers the webhook","allOf":[{"$ref":"#/components/schemas/WebhookEventType"}]},"url":{"type":"string","description":"URL where webhook notifications will be sent"},"secret":{"type":"object","description":"Secret token used for HMAC signature generation"},"createdAt":{"format":"date-time","type":"string","description":"Timestamp when the webhook subscription was created"},"updatedAt":{"format":"date-time","type":"string","description":"Timestamp when the webhook subscription was last updated"}},"required":["id","event","url","secret","createdAt","updatedAt"]},"WebhookEventType":{"type":"string","enum":["extracted","export","validation-success"]}}},"paths":{"/api/webhook/subscription/{id}":{"get":{"operationId":"WebhookController_getSubscriptionById","parameters":[{"name":"id","required":true,"in":"path","description":"Subscription ID","schema":{"type":"string"}}],"responses":{"200":{"description":"The webhook subscription details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookSubscriber"}}}},"404":{"description":"Subscription not found"},"500":{"description":"Internal server error"}},"summary":"Get webhook subscription by ID","tags":["Webhooks"]}}}}
```

## PUT /api/webhook/subscription/{id}

> Update webhook subscription

```json
{"openapi":"3.0.0","info":{"title":"Formflow API","version":"1.0"},"tags":[{"name":"Webhooks","description":"Webhook management and events"}],"security":[{"access-token":[]}],"components":{"securitySchemes":{},"schemas":{"WebhookSubscriberUpdateDto":{"type":"object","properties":{"event":{"description":"Event type that triggers the webhook","allOf":[{"$ref":"#/components/schemas/WebhookEventType"}]},"url":{"type":"string","description":"URL where webhook notifications will be sent"}}},"WebhookEventType":{"type":"string","enum":["extracted","export","validation-success"]},"WebhookSubscriber":{"type":"object","properties":{"id":{"type":"string","description":"Unique identifier of the webhook subscriber"},"event":{"description":"Event type that triggers the webhook","allOf":[{"$ref":"#/components/schemas/WebhookEventType"}]},"url":{"type":"string","description":"URL where webhook notifications will be sent"},"secret":{"type":"object","description":"Secret token used for HMAC signature generation"},"createdAt":{"format":"date-time","type":"string","description":"Timestamp when the webhook subscription was created"},"updatedAt":{"format":"date-time","type":"string","description":"Timestamp when the webhook subscription was last updated"}},"required":["id","event","url","secret","createdAt","updatedAt"]}}},"paths":{"/api/webhook/subscription/{id}":{"put":{"operationId":"WebhookController_updateSubscription","parameters":[{"name":"id","required":true,"in":"path","description":"Subscription ID","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookSubscriberUpdateDto"}}}},"responses":{"200":{"description":"The updated webhook subscription","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookSubscriber"}}}},"404":{"description":"Subscription not found"},"500":{"description":"Internal server error"}},"summary":"Update webhook subscription","tags":["Webhooks"]}}}}
```

## DELETE /api/webhook/subscription/{id}

> Delete webhook subscription

```json
{"openapi":"3.0.0","info":{"title":"Formflow API","version":"1.0"},"tags":[{"name":"Webhooks","description":"Webhook management and events"}],"security":[{"access-token":[]}],"components":{"securitySchemes":{},"schemas":{"WebhookSubscriberDeleteResponseDto":{"type":"object","properties":{"success":{"type":"boolean","description":"Indicates if the delete operation was successful"}},"required":["success"]}}},"paths":{"/api/webhook/subscription/{id}":{"delete":{"operationId":"WebhookController_deleteSubscription","parameters":[{"name":"id","required":true,"in":"path","description":"Subscription ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Subscription successfully deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookSubscriberDeleteResponseDto"}}}},"404":{"description":"Subscription not found"},"500":{"description":"Internal server error"}},"summary":"Delete webhook subscription","tags":["Webhooks"]}}}}
```


# Users

User management operations

## Get all users in the organization

> Returns all users that are members of the current organization.

```json
{"openapi":"3.0.0","info":{"title":"Formflow API","version":"1.0"},"tags":[{"name":"Users","description":"User management operations"}],"security":[{"access-token":[]}],"components":{"securitySchemes":{},"schemas":{"UserDto":{"type":"object","properties":{"id":{"type":"string","description":"Clerk user ID"},"firstName":{"type":"string","description":"User first name","nullable":true},"lastName":{"type":"string","description":"User last name","nullable":true},"imageUrl":{"type":"string","description":"User profile image URL","nullable":true}},"required":["id"]}}},"paths":{"/api/users":{"get":{"description":"Returns all users that are members of the current organization.","operationId":"UserController_getUsers","parameters":[],"responses":{"200":{"description":"Returns an array of users with their information","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/UserDto"}}}}}},"summary":"Get all users in the organization","tags":["Users"]}}}}
```


# Models

## The CreateTemplateDto object

```json
{"openapi":"3.0.0","info":{"title":"Formflow API","version":"1.0"},"components":{"schemas":{"CreateTemplateDto":{"type":"object","properties":{"name":{"type":"string","description":"The name of the template"},"description":{"type":"string","description":"The description of the template"},"version":{"type":"number","description":"The version of the template"},"schema":{"type":"object","description":"The schema of the template"},"aiGenerated":{"type":"boolean","description":"Wheter the template was generated using AI"},"vendorSchemaName":{"type":"string","description":"The vendor schema name"},"vendorSchemaVersion":{"type":"number","description":"The vendor schema version"},"vendorSchema":{"type":"object","description":"The vendor schema"},"dereferencedVendorSchema":{"type":"object","description":"The dereferenced vendor schema with resolved $ref references"},"emailAlias":{"type":"string","description":"The email alias"},"state":{"type":"string","description":"The state of the template","enum":["draft","published"],"default":"published"}},"required":["name","version"]}}}}
```

## The ImportFromLedgerDto object

```json
{"openapi":"3.0.0","info":{"title":"Formflow API","version":"1.0"},"components":{"schemas":{"ImportFromLedgerDto":{"type":"object","properties":{"schema":{"type":"object","description":"The updated schema from Ledger"},"vendorSchemaName":{"type":"string","description":"The vendor schema name"},"vendor_schema_name":{"type":"string","description":"The vendor schema name (alternative)"},"vendorSchemaVersion":{"type":"number","description":"The vendor schema version"},"vendor_schema_version":{"type":"number","description":"The vendor schema version (alternative)"},"vendorSchema":{"type":"object","description":"The vendor schema"},"vendor_schema":{"type":"object","description":"The vendor schema (alternative snake_case)"},"dereferencedVendorSchema":{"type":"object","description":"The dereferenced vendor schema with resolved $ref references"}}}}}}
```

## The UpdateTemplateDto object

```json
{"openapi":"3.0.0","info":{"title":"Formflow API","version":"1.0"},"components":{"schemas":{"UpdateTemplateDto":{"type":"object","properties":{"name":{"type":"string","description":"The name of the template"},"description":{"type":"string","description":"The description of the template"},"version":{"type":"number","description":"The version of the template"},"schema":{"type":"object","description":"The schema of the template"},"vendorSchemaName":{"type":"string","description":"The vendor schema name"},"vendorSchemaVersion":{"type":"number","description":"The vendor schema version"},"vendorSchema":{"type":"object","description":"The vendor schema"},"dereferencedVendorSchema":{"type":"object","description":"The dereferenced vendor schema with resolved $ref references"},"emailAlias":{"type":"string","description":"The email alias"},"extractionStrategyId":{"type":"string","description":"The uuid of extraction strategy template uses."},"state":{"type":"string","description":"The state of the template","enum":["draft","published"],"default":"published"},"parsingInstructions":{"type":"object","description":"the parsing instructions for a template"}}}}}}
```

## The FileDto object

```json
{"openapi":"3.0.0","info":{"title":"Formflow API","version":"1.0"},"components":{"schemas":{"FileDto":{"type":"object","properties":{"id":{"type":"string","description":"Unique identifier for the file"},"name":{"type":"string","description":"Name of the file"},"fileName":{"type":"string","description":"Original file name"},"fileType":{"type":"string","description":"Type of the file (e.g., MIME type)"},"fileSize":{"type":"number","description":"Size of the file in bytes"},"url":{"type":"string","description":"Public URL of the file"},"createdAt":{"format":"date-time","type":"string","description":"Timestamp when the file was created"},"updatedAt":{"format":"date-time","type":"string","description":"Timestamp when the file was last updated"}},"required":["id","fileName","fileType","fileSize","url","createdAt","updatedAt"]}}}}
```

## The Event object

```json
{"openapi":"3.0.0","info":{"title":"Formflow API","version":"1.0"},"components":{"schemas":{"Event":{"type":"object","properties":{"id":{"type":"string","description":"Unique identifier for the event"},"submissionId":{"type":"string","description":"Related submission ID","nullable":true},"type":{"type":"string","description":"Type of event","enum":["upload","email","conversion","extraction","export","discard","reference","generate-schema","metadata"]},"state":{"type":"string","description":"Current state of the event","enum":["pending","succeed","fail"]},"createdAt":{"format":"date-time","type":"string","description":"Timestamp when the event was created"},"updatedAt":{"format":"date-time","type":"string","description":"Timestamp when the event was last updated"}},"required":["id","submissionId","type","state","createdAt","updatedAt"]}}}}
```

## The CreateSubmissionDto object

```json
{"openapi":"3.0.0","info":{"title":"Formflow API","version":"1.0"},"components":{"schemas":{"CreateSubmissionDto":{"type":"object","properties":{"id":{"type":"string","description":"Unique identifier for the submission"},"name":{"type":"string","description":"Submission name"},"templateId":{"type":"number","description":"ID of the associated template"},"uploaderUserId":{"type":"string","description":"ID of the user who uploaded the submission"},"userId":{"type":"string","description":"ID of the user associated with the submission"},"emailSender":{"type":"string","description":"Email address of the sender"},"emailBody":{"type":"string","description":"Email body or additional context"},"emailMessageId":{"type":"string","description":"Email message ID"},"payload":{"type":"object","description":"Submission payload data"},"manualPayload":{"type":"object","description":"Manual payload data"},"metaPayload":{"type":"object","description":"Metadata payload"}}}}}}
```

## The Strategy object

```json
{"openapi":"3.0.0","info":{"title":"Formflow API","version":"1.0"},"components":{"schemas":{"Strategy":{"type":"object","properties":{"id":{"type":"string","description":"Unique identifier for the strategy"},"identifier":{"type":"string","description":"Identifier of the strategy type","enum":["extraction-claude-3-7","extraction-gemini-2-5-loose","extraction-claude-3-7-thinking","extraction-atomic"]},"type":{"type":"string","description":"Type of the strategy","default":"extraction"},"displayName":{"type":"string","description":"Display name of the strategy"},"description":{"type":"string","description":"Description of the strategy"},"createdAt":{"format":"date-time","type":"string","description":"Timestamp when the strategy was created"},"updatedAt":{"format":"date-time","type":"string","description":"Timestamp when the strategy was last updated"}},"required":["id","identifier","type","displayName","createdAt","updatedAt"]}}}}
```

## The Template object

```json
{"openapi":"3.0.0","info":{"title":"Formflow API","version":"1.0"},"components":{"schemas":{"Template":{"type":"object","properties":{"id":{"type":"number","description":"The ID of the template"},"templateId":{"type":"string","description":"The UUID of the template"},"name":{"type":"string","description":"The name of the template"},"description":{"type":"string","description":"The description of the template"},"version":{"type":"number","description":"The version of the template"},"schema":{"type":"object","description":"The schema of the template"},"vendorSchemaName":{"type":"string","description":"The vendor schema name"},"vendorSchemaVersion":{"type":"number","description":"The vendor schema version"},"vendorSchema":{"type":"object","description":"The vendor schema"},"dereferencedVendorSchema":{"type":"object","description":"The dereferenced vendor schema with resolved $ref references"},"aiGenerated":{"type":"boolean","description":"Whether the template was generated using AI"},"emailAlias":{"type":"string","description":"The email alias"},"extractionStrategyId":{"type":"string","description":"The uuid of extraction strategy template uses."},"extractionStrategy":{"description":"Associated extraction strategy","allOf":[{"$ref":"#/components/schemas/Strategy"}]},"referenceStrategy":{"description":"Associated reference strategy","allOf":[{"$ref":"#/components/schemas/Strategy"}]},"state":{"type":"string","description":"The state of the template","enum":["draft","published"],"default":"published"},"parsingInstructions":{"type":"object","description":"the parsing instructions for a template"}},"required":["id","templateId","name","version","aiGenerated","extractionStrategyId","extractionStrategy","referenceStrategy","state","parsingInstructions"]},"Strategy":{"type":"object","properties":{"id":{"type":"string","description":"Unique identifier for the strategy"},"identifier":{"type":"string","description":"Identifier of the strategy type","enum":["extraction-claude-3-7","extraction-gemini-2-5-loose","extraction-claude-3-7-thinking","extraction-atomic"]},"type":{"type":"string","description":"Type of the strategy","default":"extraction"},"displayName":{"type":"string","description":"Display name of the strategy"},"description":{"type":"string","description":"Description of the strategy"},"createdAt":{"format":"date-time","type":"string","description":"Timestamp when the strategy was created"},"updatedAt":{"format":"date-time","type":"string","description":"Timestamp when the strategy was last updated"}},"required":["id","identifier","type","displayName","createdAt","updatedAt"]}}}}
```

## The SubmissionStatus object

```json
{"openapi":"3.0.0","info":{"title":"Formflow API","version":"1.0"},"components":{"schemas":{"SubmissionStatus":{"type":"string","enum":["created","processing","processing-failed","processed","in-review","completed","exported","export-failed","discarded","preparing","queued"]}}}}
```

## The SubmissionDto object

```json
{"openapi":"3.0.0","info":{"title":"Formflow API","version":"1.0"},"components":{"schemas":{"SubmissionDto":{"type":"object","properties":{"id":{"type":"string","description":"Unique identifier for the submission"},"submissionNumber":{"type":"number","description":"Submission sequence number"},"name":{"type":"string","description":"Submission name"},"template":{"description":"Associated template","allOf":[{"$ref":"#/components/schemas/Template"}]},"templateId":{"type":"number","description":"ID of the associated template"},"uploaderUserId":{"type":"string","description":"ID of the user who uploaded the submission"},"userId":{"type":"string","description":"ID of the user associated with the submission"},"files":{"description":"Files attached to the submission","type":"array","items":{"type":"string"}},"status":{"description":"Current status of the submission","allOf":[{"$ref":"#/components/schemas/SubmissionStatus"}]},"emailSender":{"type":"string","description":"Email address of the sender"},"emailBody":{"type":"string","description":"Email body or additional context"},"emailMessageId":{"type":"string","description":"Email message ID"},"payload":{"type":"object","description":"Submission payload data"},"manualPayload":{"type":"object","description":"Manual payload data"},"metaPayload":{"type":"object","description":"Metadata payload"},"OBEExtraction":{"type":"string","description":"ID of the old backend extraction event"},"NBEExtraction":{"type":"string","description":"ID of the new backend extraction event"},"createdAt":{"format":"date-time","type":"string","description":"Timestamp when the submission was created"},"updatedAt":{"format":"date-time","type":"string","description":"Timestamp when the submission was last updated"}},"required":["id","submissionNumber","name","template","templateId","files","status","createdAt","updatedAt"]},"Template":{"type":"object","properties":{"id":{"type":"number","description":"The ID of the template"},"templateId":{"type":"string","description":"The UUID of the template"},"name":{"type":"string","description":"The name of the template"},"description":{"type":"string","description":"The description of the template"},"version":{"type":"number","description":"The version of the template"},"schema":{"type":"object","description":"The schema of the template"},"vendorSchemaName":{"type":"string","description":"The vendor schema name"},"vendorSchemaVersion":{"type":"number","description":"The vendor schema version"},"vendorSchema":{"type":"object","description":"The vendor schema"},"dereferencedVendorSchema":{"type":"object","description":"The dereferenced vendor schema with resolved $ref references"},"aiGenerated":{"type":"boolean","description":"Whether the template was generated using AI"},"emailAlias":{"type":"string","description":"The email alias"},"extractionStrategyId":{"type":"string","description":"The uuid of extraction strategy template uses."},"extractionStrategy":{"description":"Associated extraction strategy","allOf":[{"$ref":"#/components/schemas/Strategy"}]},"referenceStrategy":{"description":"Associated reference strategy","allOf":[{"$ref":"#/components/schemas/Strategy"}]},"state":{"type":"string","description":"The state of the template","enum":["draft","published"],"default":"published"},"parsingInstructions":{"type":"object","description":"the parsing instructions for a template"}},"required":["id","templateId","name","version","aiGenerated","extractionStrategyId","extractionStrategy","referenceStrategy","state","parsingInstructions"]},"Strategy":{"type":"object","properties":{"id":{"type":"string","description":"Unique identifier for the strategy"},"identifier":{"type":"string","description":"Identifier of the strategy type","enum":["extraction-claude-3-7","extraction-gemini-2-5-loose","extraction-claude-3-7-thinking","extraction-atomic"]},"type":{"type":"string","description":"Type of the strategy","default":"extraction"},"displayName":{"type":"string","description":"Display name of the strategy"},"description":{"type":"string","description":"Description of the strategy"},"createdAt":{"format":"date-time","type":"string","description":"Timestamp when the strategy was created"},"updatedAt":{"format":"date-time","type":"string","description":"Timestamp when the strategy was last updated"}},"required":["id","identifier","type","displayName","createdAt","updatedAt"]},"SubmissionStatus":{"type":"string","enum":["created","processing","processing-failed","processed","in-review","completed","exported","export-failed","discarded","preparing","queued"]}}}}
```

## The UploadRequestDto object

```json
{"openapi":"3.0.0","info":{"title":"Formflow API","version":"1.0"},"components":{"schemas":{"UploadRequestDto":{"type":"object","properties":{"fileName":{"type":"string","description":"Name of the file to upload"},"contentType":{"type":"string","description":"MIME type of the file"}},"required":["fileName","contentType"]}}}}
```

## The UpdateSubmissionDto object

```json
{"openapi":"3.0.0","info":{"title":"Formflow API","version":"1.0"},"components":{"schemas":{"UpdateSubmissionDto":{"type":"object","properties":{"id":{"type":"string","description":"Unique identifier for the submission"},"submissionNumber":{"type":"number","description":"Submission sequence number"},"name":{"type":"string","description":"Submission name"},"templateId":{"type":"number","description":"ID of the associated template"},"uploaderUserId":{"type":"string","description":"ID of the user who uploaded the submission"},"userId":{"type":"string","description":"ID of the user associated with the submission"},"status":{"description":"Current status of the submission","allOf":[{"$ref":"#/components/schemas/SubmissionStatus"}]},"emailSender":{"type":"string","description":"Email address of the sender"},"emailBody":{"type":"string","description":"Email body or additional context"},"emailMessageId":{"type":"string","description":"Email message ID"},"payload":{"type":"object","description":"Submission payload data"},"manualPayload":{"type":"object","description":"Manual payload data"},"metaPayload":{"type":"object","description":"Metadata payload"},"createdAt":{"format":"date-time","type":"string","description":"Timestamp when the submission was created"},"updatedAt":{"format":"date-time","type":"string","description":"Timestamp when the submission was last updated"}}},"SubmissionStatus":{"type":"string","enum":["created","processing","processing-failed","processed","in-review","completed","exported","export-failed","discarded","preparing","queued"]}}}}
```

## The WebhookEventInfoDto object

```json
{"openapi":"3.0.0","info":{"title":"Formflow API","version":"1.0"},"components":{"schemas":{"WebhookEventInfoDto":{"type":"object","properties":{"eventType":{"type":"string","description":"Event type identifier"},"description":{"type":"string","description":"Human-readable description of when this event is triggered"},"examplePayload":{"type":"object","description":"Example payload structure for this event type"}},"required":["eventType","description","examplePayload"]}}}}
```

## The WebhookEventType object

```json
{"openapi":"3.0.0","info":{"title":"Formflow API","version":"1.0"},"components":{"schemas":{"WebhookEventType":{"type":"string","enum":["extracted","export","validation-success"]}}}}
```

## The WebhookSubscriberCreateDto object

```json
{"openapi":"3.0.0","info":{"title":"Formflow API","version":"1.0"},"components":{"schemas":{"WebhookSubscriberCreateDto":{"type":"object","properties":{"event":{"description":"Event type that triggers the webhook","allOf":[{"$ref":"#/components/schemas/WebhookEventType"}]},"url":{"type":"string","description":"URL where webhook notifications will be sent"}},"required":["event","url"]},"WebhookEventType":{"type":"string","enum":["extracted","export","validation-success"]}}}}
```

## The WebhookSubscriber object

```json
{"openapi":"3.0.0","info":{"title":"Formflow API","version":"1.0"},"components":{"schemas":{"WebhookSubscriber":{"type":"object","properties":{"id":{"type":"string","description":"Unique identifier of the webhook subscriber"},"event":{"description":"Event type that triggers the webhook","allOf":[{"$ref":"#/components/schemas/WebhookEventType"}]},"url":{"type":"string","description":"URL where webhook notifications will be sent"},"secret":{"type":"object","description":"Secret token used for HMAC signature generation"},"createdAt":{"format":"date-time","type":"string","description":"Timestamp when the webhook subscription was created"},"updatedAt":{"format":"date-time","type":"string","description":"Timestamp when the webhook subscription was last updated"}},"required":["id","event","url","secret","createdAt","updatedAt"]},"WebhookEventType":{"type":"string","enum":["extracted","export","validation-success"]}}}}
```

## The WebhookSubscriberUpdateDto object

```json
{"openapi":"3.0.0","info":{"title":"Formflow API","version":"1.0"},"components":{"schemas":{"WebhookSubscriberUpdateDto":{"type":"object","properties":{"event":{"description":"Event type that triggers the webhook","allOf":[{"$ref":"#/components/schemas/WebhookEventType"}]},"url":{"type":"string","description":"URL where webhook notifications will be sent"}}},"WebhookEventType":{"type":"string","enum":["extracted","export","validation-success"]}}}}
```

## The WebhookSubscriberDeleteResponseDto object

```json
{"openapi":"3.0.0","info":{"title":"Formflow API","version":"1.0"},"components":{"schemas":{"WebhookSubscriberDeleteResponseDto":{"type":"object","properties":{"success":{"type":"boolean","description":"Indicates if the delete operation was successful"}},"required":["success"]}}}}
```

## The UserDto object

```json
{"openapi":"3.0.0","info":{"title":"Formflow API","version":"1.0"},"components":{"schemas":{"UserDto":{"type":"object","properties":{"id":{"type":"string","description":"Clerk user ID"},"firstName":{"type":"string","description":"User first name","nullable":true},"lastName":{"type":"string","description":"User last name","nullable":true},"imageUrl":{"type":"string","description":"User profile image URL","nullable":true}},"required":["id"]}}}}
```

## The SubmissionEntryDTO object

```json
{"openapi":"3.0.0","info":{"title":"Formflow API","version":"1.0"},"components":{"schemas":{"SubmissionEntryDTO":{"type":"object","properties":{"submissionId":{"type":"string","description":"The ID of the submission"},"generateTemplate":{"type":"boolean","description":"Whether to generate a template for the submission","default":false}},"required":["submissionId"]}}}}
```

## The SubmissionResponseDto object

```json
{"openapi":"3.0.0","info":{"title":"Formflow API","version":"1.0"},"components":{"schemas":{"SubmissionResponseDto":{"type":"object","properties":{"submissionId":{"type":"string","description":"The ID of the submission"},"message":{"type":"string","description":"Response message"},"templateId":{"type":"number","description":"The ID of the template if generated"}},"required":["submissionId","message"]}}}}
```

## The CreateApiClientsRequestDto object

```json
{"openapi":"3.0.0","info":{"title":"Formflow API","version":"1.0"},"components":{"schemas":{"CreateApiClientsRequestDto":{"type":"object","properties":{}}}}}
```

## The CreateApiClientsResponseDto object

```json
{"openapi":"3.0.0","info":{"title":"Formflow API","version":"1.0"},"components":{"schemas":{"CreateApiClientsResponseDto":{"type":"object","properties":{"clientSecret":{"type":"string","description":"The raw client secret (only shown once during creation)"},"message":{"type":"string","description":"Important message about storing the secret"}},"required":["clientSecret","message"]}}}}
```

## The ApiClient object

```json
{"openapi":"3.0.0","info":{"title":"Formflow API","version":"1.0"},"components":{"schemas":{"ApiClient":{"type":"object","properties":{}}}}}
```

## The ListApiClientsResponseDto object

```json
{"openapi":"3.0.0","info":{"title":"Formflow API","version":"1.0"},"components":{"schemas":{"ListApiClientsResponseDto":{"type":"object","properties":{"clients":{"type":"array","items":{"$ref":"#/components/schemas/ApiClient"}}},"required":["clients"]},"ApiClient":{"type":"object","properties":{}}}}}
```

## The RevokeApiClientResponseDto object

```json
{"openapi":"3.0.0","info":{"title":"Formflow API","version":"1.0"},"components":{"schemas":{"RevokeApiClientResponseDto":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"]}}}}
```

## The RotateSecretResponseDto object

```json
{"openapi":"3.0.0","info":{"title":"Formflow API","version":"1.0"},"components":{"schemas":{"RotateSecretResponseDto":{"type":"object","properties":{"clientId":{"type":"string"},"clientSecret":{"type":"string"},"message":{"type":"string"}},"required":["clientId","clientSecret","message"]}}}}
```

## The ApiClientsRequestDto object

```json
{"openapi":"3.0.0","info":{"title":"Formflow API","version":"1.0"},"components":{"schemas":{"ApiClientsRequestDto":{"type":"object","properties":{"clientId":{"type":"string","description":"The API client ID provided when the API client was created"},"clientSecret":{"type":"string","description":"The API client secret provided when the API client was created"},"organizationId":{"type":"string","description":"The organization ID associated with this API client"}},"required":["clientId","clientSecret","organizationId"]}}}}
```

## The GetTokenResponseDto object

```json
{"openapi":"3.0.0","info":{"title":"Formflow API","version":"1.0"},"components":{"schemas":{"GetTokenResponseDto":{"type":"object","properties":{"token":{"type":"string","description":"JWT access token to use for authenticated API requests"},"token_type":{"type":"string","description":"Type of token (always \"Bearer\")"},"expires_in":{"type":"number","description":"Token expiration time in seconds"}},"required":["token","token_type","expires_in"]}}}}
```


# DSP factsheet

Data privacy, security controls, retention, and compliance posture for Nora.

### Introduction

At Insly, we prioritize privacy and security. We follow the latest standards and compliance measures across infrastructure, processes, and product design.

### Data privacy

Insly safeguards user privacy by strictly following applicable privacy laws. Insly is the data ‘controller’ of any information provided. This information remains secure and confidential.

### Data minimization and anonymization

Only essential data is extracted to provide the requested services. Any unnecessary Personally Identifiable Information (PII) is anonymized or discarded. Insly limits data collection to the minimum required for service delivery.

### Data security practices

To meet the highest standards in data security, Insly upholds the following principles:

* Strict data silos between tenants.
* SOC 2 Type II certified identification and authentication layer.
* Role-Based Access Control (RBAC) to enforce least privilege access across all roles.
* Fully GDPR-compliant data processing and sub-processor practices.
* Agreements with AI providers to meet enterprise-grade privacy standards.

#### Database-level data security

* Robust organization-based tenancy model keeps each client’s data isolated and secure.
* Advanced database security with connection-level access control maintains strict data silos across testing and production accounts.
* AES-256 encryption over TLS 1.3 for all network traffic ensures data confidentiality and protection.

#### Access control

* Dynamic Role-Based Access Control (RBAC) adjusts to meet organizational requirements, offering multi-level permissions.
* Quarterly access audits and ad-hoc access reviews ensure adherence to the least privilege principle.
* SOC 2 Type II certified authentication for secure access.

### Purpose limitation and restricted retention

Data collected is used solely for contracted purposes.

### Transparency in data use

Insly may use aggregated and anonymized data from extracted content to improve AI models or enhance service functionality. No individual data points are identifiable. Complete confidentiality is maintained.

### Data processing and file handling agreements with LLM-related service providers

* Strict data handling agreements with LLM-related service providers mandate data deletion post-processing.
* Regular assessments of tools, including offerings by Anthropic, OpenAI, MS Azure, for compliance with privacy regulations.
* Customizable document retention period, with AES-256 encryption securing all files at rest.

### Monitoring and incident response

* Real-time monitoring with expedited response mechanisms for critical incidents.
* Regular training equips employees to handle sensitive data and recognize security risks.
* The response includes detection, containment, eradication, and recovery steps to mitigate any security incident.
* Incident response transparency: Clients are notified promptly of any data breach, including mitigation steps and timelines.

### Sub-processor transparency and accountability

For transparency, Insly provides a list of all third-party sub-processors used in data processing upon request. Each processor follows strict security protocols to ensure data protection compliance.

### User rights and data requests

Users have the right to access, correct, or delete their data. Requests can be submitted through Insly’s contact channels. Responses are provided within 30 days, subject to verification requirements.

### Compliance certifications and continuous monitoring

Insly performs regular audits and penetration tests as part of ongoing security monitoring to maintain high security standards. Automated tools and processes for compliance verification are in place.

### Client-specific data protection options

Insly offers enhanced data protection measures upon request, including additional encryption and custom data deletion options for clients with specific regulatory needs.

### Contact us

For detailed information or to report a security concern, contact:

* <info@insly.com>


