> ## Documentation Index
> Fetch the complete documentation index at: https://seal-d2ca5bea.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Scripts

> Seal SDK reference and scripting documentation.

Automations are configuration logic embedded in an entity. They read and write fields on that entity — setting record IDs, populating fields, generating labels, enforcing validation rules.

<Note>
  **Prefer [formulas](/entity-content/formulas) for new work.** Formulas are smaller, scoped to one field, and run in topo order — they're more efficient and easier to debug. When an entity has any formula, the entity-level automation script is bypassed entirely (the Automation tab shows a callout). Existing automations keep working; consider porting them on next change.
</Note>

An automation runs inside an `Entity()` context. Seal pre-loads the entity's data, the automation reads and writes fields, and when the context exits Seal applies the updates to that entity.

```python theme={null}
with Entity() as entity:
    if not entity.fields.record_id:
        siblings = entity.get_field_refs()
        entity.fields.record_id = f"REC-{len(siblings):04d}"
```

Automations can also read referenced entities and their fields, import reusable functions from other automations via `load()`, and use helper functions like `now()`, `days()`, `skip()`, and `get_user()`.

> **Controlled fields.** When you save an automation, Seal analyses the code to determine which fields it writes to and marks them as "controlled." Only controlled fields accept updates from the automation.

# Triggers

## What is a trigger?

* A trigger lives on an automation and runs it automatically at a particular event.
* These triggering events can be
  * Event based: This means they have been caused by edits or changes to particular entities
  * Time based: e.g. every 5 minutes

## How to set up a trigger

Navigate to the automation that you want to be triggered. In the sidebar, you will see a 'Triggers' tab where you can view, edit, remove, and create triggers.

You can **view** (but not edit) all existing triggers from the organisation settings page. For each trigger here you can see:

* what event will trigger it
* what automation it applies to
* all runs of the trigger, their status, timings and logs

In the audit log, the 'user' who runs the automation and is responsible for its actions will be the 'trigger runner'.

## Different trigger events

### Entity dependent:

These events all relate to changes to certain entities, you can pick which entities classify when you make the event.

1. Choose whether you want the trigger to apply to changes to Templates, Instances, or both
2. Choose specific entities that you want to trigger on.

If you choose 'INSTANCE' , but you then select a specific template entity, this means the trigger will apply to **all instances of that template**.

If you choose 'INSTANCE' and select an instance, that trigger will **only look for trigger events on that singular entity**, likewise if you choose 'TEMPLATE' and select a template.

| Entity dependent event | Description                                                                                 | Notes                                                                                                      |
| ---------------------- | ------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- |
| `onMoveOutOfEditable`  | Runs the automation whenever a chosen entity becomes non-editable                           | The only way for entities to become 'non-editable' are to be sent for review or published                  |
| `onPublish`            | Runs the automation whenever a chosen entity is published (transitions to PUBLISHED status) | This trigger fires for all entities transitioning to PUBLISHED, whether from EDITABLE or IN\_REVIEW status |
| `onCreate`             | Runs the automation when a chosen entity is created                                         | This trigger will only work when INSTANCE has been chosen, and a specific template selected.               |
| `onArchive`            | Runs the automation when a chosen entity is archived                                        | -                                                                                                          |
| `onRecover`            | Runs the automation when a chosen entity is recovered                                       | -                                                                                                          |

As these triggers are all entity dependent, the automation runs with the context of the entity that triggered it.

E.g. if the automation adds a tag to the entity, and the trigger is `onCreate` — every time an entity is created that matches the conditions, the automation tags that particular entity.

### Entity independent:

Entity independent triggers allow automations to run autonomously, without being directly tied to a parent entity. These automations can still interact with and perform actions on various entities, but they operate independently from any specific entity.

| Name         | Description                                                                                   | Notes                                                                                                                 |
| ------------ | --------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
| `onSchedule` | Runs the automation at predetermined intervals, such as every 5 minutes or at midnight daily. | Scheduled times are evaluated in the UTC timezone, ensuring consistent execution regardless of local time variations. |

Entity independent automations are particularly useful for tasks that require regular execution or maintenance, such as cleanup processes or periodic data analysis, without the need for entity-specific context.

# The Seal SDK

The Seal module is a Python package for interacting programmatically with the Seal platform. It provides a secure and audited method for administrators to perform configuration and automation.

It is automatically available in every automation's Python environment — you don't need to import it manually.

The Seal module consists of a collection of properties and methods on the `seal` object:

### Script context

Every script has access to properties describing the current run. Use these to identify the script, its environment, and how it was triggered — without hardcoding IDs.

| Property                     | Type                  | Description                                                                                                                                 |
| ---------------------------- | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| `seal.entity_id`             | `str`                 | The entity ID of the script itself. Use `seal.get_entity(seal.entity_id)` to get the full entity data.                                      |
| `seal.embedded_in_entity_id` | `str \| None`         | The entity ID of the entity the script is embedded in (e.g. the entity where the button was pressed). `None` if the script is run directly. |
| `seal.validating_entity_id`  | `str \| None`         | The entity ID being validated. Only set when running as a validation script.                                                                |
| `seal.trigger_info`          | `TriggerInfo \| None` | Trigger metadata — access via `seal.get_trigger_info()`. Only set when the script was invoked by a trigger.                                 |
| `seal.script_run_id`         | `str \| None`         | Unique ID for this execution run.                                                                                                           |

### Getting entities

```python theme={null}
seal.get_entity(entity_id, ?version)
seal.get_entity(ref={"id: "...", "version": "..."})
```

Returns the entire json blob of data for that entity. The entity id can be copied from the entity's URL in Seal - it's the final section of the url, after the entity title.\
Passing an *`entity_id`* returns the latest version or draft of the entity.

The entity id can be copied from the entity's URL in Seal - it's the final section of the url, after the entity title.

To get a specific version of an entity, include the version as a second argument. You can also pass an entity ref object instead.

Since it returns the whole json blob for the entity, you can get the entity's fields via `entity["fields"]` , or a specific field via `entity["fields"]["fieldName"]` .

```python theme={null}
seal.get_entity_active_version(entity_id)
```

Gets the data for the active version of the given entity. If no active version is found (ie the entity has never been published), an error will be thrown.

```python theme={null}
seal.get_containing_entity()
```

Gets the entity the automation is embedded in.

```python theme={null}
seal.is_test_entity(entity_id)
```

Returns a boolean indicating whether or not an entity is a test entity.

```python theme={null}
seal.is_containing_entity_a_test_entity()
```

Returns a boolean indicating whether or not the entity the automation is embedded in is a test entity. Can only be run from an automation field.

```python theme={null}
seal.get_entities_by_title(title)
```

Gets (non-archived) entities by their title (string). Note that exact titles are matched by default. Since multiple entities can share the same title, this returns an array. You can also search for titles containing the search string with `exact=False`.

```python theme={null}
seal.search_entities(query_config)
```

Gets entities using a query config object (dict).

> **Query results return active entity state.** For published entities, query results reflect the published snapshot (not the current draft). Unpublished entities return their draft data. This ensures the data you see matches the filters you queried with. The Python SDK (`seal.search_entities`) uses the v1 API and defaults to draft data for backwards compatibility — pass `searchType: "ACTIVE"` in the query config to read the published snapshot instead.

#### Query Config Structure

```python theme={null}
query_config = {
    "filters": {
        "and": [
            {"filter": "type", "operator": "in", "value": ["Sample"]},
            {"filter": "status", "operator": "in", "value": ["FINISHED"]}
        ]
    },
    "orderBy": [{"type": "metadata", "name": "LAST_UPDATED_AT", "direction": "DESC"}],
    "limit": 100,
    "excludeInitialDrafts": false  # set true to exclude entities without a published version
}

results = seal.search_entities(query_config)
```

#### Available Filters

| Filter          | Value Type | Example                                                   |
| --------------- | ---------- | --------------------------------------------------------- |
| `type`          | string\[]  | `["Sample", "Procedure"]`                                 |
| `kind`          | enum\[]    | `["INSTANCE", "TEMPLATE", "TYPE"]`                        |
| `status`        | enum\[]    | `["EDITABLE", "IN_REVIEW", "FINISHED"]`                   |
| `template`      | uuid\[]    | `["template-uuid"]`                                       |
| `tag`           | uuid\[]    | `["tag-uuid"]`                                            |
| `archived`      | enum\[]    | `["TRUE"]` or `["FALSE"]`                                 |
| `createdBy`     | string\[]  | `["user-role-id"]`                                        |
| `lastUpdatedBy` | string\[]  | `["user-role-id"]`                                        |
| `assignees`     | uuid\[]    | `["role-id"]`                                             |
| `submittedFrom` | uuid\[]    | `["entity-uuid"]`                                         |
| `field`         | string\[]  | `["Field Name"]` - entities containing this field         |
| `fieldValue`    | object\[]  | `[{"name": "Field Name", "operator": "=", "value": "X"}]` |
| `statusTag`     | string\[]  | `["Published", "Draft"]`                                  |
| `text`          | string\[]  | `["search term"]` - title search                          |
| `changeSet`     | string\[]  | `["change-set-index"]`                                    |
| `hasTrigger`    | enum\[]    | `["TRUE"]` or `["FALSE"]`                                 |
| `system`        | string\[]  | `["system-name"]`                                         |
| `contentType`   | enum\[]    | `["Page", "Script", "File", "Chart"]`                     |

#### Filter Operators

* `in`: Match any value in list
* `not_in`: Exclude values in list

#### Field Value Operators

For `fieldValue` filters, the following operators are available: `=`, `>`, `<`, `>=`, `<=`

```python theme={null}
{"filter": "fieldValue", "operator": "in", "value": [{"name": "Temperature", "operator": ">=", "value": "20"}]}
```

#### Combining Filters (AND/OR)

Filters within `and` are combined with AND logic. Use `or` for OR logic within an AND group:

```python theme={null}
query_config = {
    "filters": {
        "and": [
            {"filter": "type", "operator": "in", "value": ["Sample"]},
            {"or": [
                {"filter": "status", "operator": "in", "value": ["EDITABLE"]},
                {"filter": "status", "operator": "in", "value": ["FINISHED"]}
            ]}
        ]
    }
}
```

#### Sorting Results

```python theme={null}
"orderBy": [
    {"type": "metadata", "name": "LAST_UPDATED_AT", "direction": "DESC"},
    {"type": "metadata", "name": "CREATED_AT", "direction": "ASC"},
    {"type": "field", "name": "My Field Name", "direction": "DESC"},
    {"type": "root", "name": "TITLE", "direction": "ASC"}
]
```

Available metadata columns: `CREATED_AT`, `LAST_UPDATED_AT`, `CREATED_BY`, `LAST_UPDATED_BY`

Available root columns: `TITLE`, `TYPE`, `TEMPLATED_FROM`, `SUBMITTED_FROM`, `CREATED_FROM`

### Making an entity editable

```python theme={null}
seal.make_entity_editable(entity_id)
```

Used to make an entity editable (ie create a new draft). Returns the entire json blob of data for the new draft entity. If the entity is already editable, this will make no changes and return the current draft.

### Reverting entity drafts

```python theme={null}
seal.revert_entities(entity_ids)
```

Revert multiple entity drafts to their previous published versions. All entities must be in EDITABLE status and have a previous version. Up to 500 entities can be reverted at once.

Parameters:

* `entity_ids`: List of entity IDs to revert (maximum 500)

Returns an array of the reverted entities.

```python theme={null}
seal.revert_containing_entity()
```

Revert the entity the automation is embedded in to its previous published version. The entity must be in EDITABLE status and have a previous version.

Returns an array containing the reverted entity.

### Validating entities

Automations can be used to run checks before an entity is published. An automation can be added as a check on a type or template for its templates or instances.

```python theme={null}
seal.get_validating_entity()
```

Gets the entity an automation is validating.

When a validation automation is run, it is classed as passing if no errors are thrown. Any of the following methods will throw an error if the assertion is not met:

| Method                               | Checks that          |
| ------------------------------------ | -------------------- |
| validation.assertEqual(a, b)         | a == b               |
| validation.assertNotEqual(a, b)      | a != b               |
| validation.assertTrue(x)             | bool(x) is True      |
| validation.assertFalse(x)            | bool(x) is False     |
| validation.assertIs(a, b)            | a is b               |
| validation.assertIsNot(a, b)         | a is not b           |
| validation.assertIsNone(x)           | x is None            |
| validation.assertIsNotNone(x)        | x is not None        |
| validation.assertIn(a, b)            | a in b               |
| validation.assertNotIn(a, b)         | a not in b           |
| validation.assertIsInstance(a, b)    | isinstance(a, b)     |
| validation.assertNotIsInstance(a, b) | not isinstance(a, b) |

User-defined errors can also be thrown to fail a validation automation.

```python theme={null}
seal.throw_error(message)
```

Throws an error with a user-provided message.

### Archiving an entity

```python theme={null}
seal.archive_entity(entity_id, archive)
```

Archives or unarchives an entity. The `archive` parameter should be a boolean: `True` to archive, `False` to recover. Returns the updated entity data.

```python theme={null}
seal.archive_entities(entity_ids)
```

Archives multiple entities. Must pass in an array of entity\_id values. Returns the ids that were archived.

### Converting an Instance to a Template

```python theme={null}
seal.convert_instance_to_template(entity_id)
```

Converts an instance entity into a template entity. The instance must be editable (a draft) to be converted. Returns the entire json blob of data for the new template entity.

### Converting an Entity to a Different Type

```python theme={null}
seal.convert_entity_type_in_entity(entity_id, new_type_id=None, new_kind=None)
```

Converts an entity to a different type and/or kind. At least one of `new_type_id` or `new_kind` must be provided.

```python theme={null}
seal.convert_entity_type(new_type_id=None, new_kind=None)
```

Same as above but for the entity the automation block is embedded in.

Parameters:

* `entity_id`: The ID of the entity to convert
* `new_type_id`: The ID of the new Type to convert the entity to (optional)
* `new_kind`: The new kind to convert to, either `'TEMPLATE'` or `'INSTANCE'` (optional)

The entity must be editable (a draft). Cannot convert test entities, Type entities, or placeholders to a different type.

### Updating the status tag on an entity

```python theme={null}
seal.update_entity_status_tag(entity_id, status_tag)
```

Updates the status tag on an entity. The live entity must be in an editable state.

```python theme={null}
seal.update_upcoming_entity_status_tag(entity_id, status_tag)
```

Updates the upcoming status tag an entity will have when it is next published. The live entity must be in an editable state.

### Getting the upcoming version info of an entity

```python theme={null}
seal.get_upcoming_version_info(entity_id)
```

Returns the upcoming version info of an entity. This includes the version and status tag.

### Adding an entity to a change set

```python theme={null}
seal.add_entity_to_change_set(entity_id, change_set_index)
```

Used to add an entity to a pre-existing change set. The `change_set_index` can be found in the URL when on a change set page, or you can pass the whole change-set dict returned by `get_change_set_for_entity`.

Returns information about the change set the provided entity now belongs to. This information includes id, index, name, status and description.

### Adding multiple entities to a change set

```python theme={null}
seal.add_entities_to_change_set(entity_ids, change_set_index)
```

Used to add multiple entities to a pre-existing change set. The `entity_ids` parameter should be a list of entity IDs, and the `change_set_index` can be found in the URL when on a change set page, or you can pass the whole change-set dict returned by `get_change_set_for_entity`.

Returns information about the change set that the provided entities now belong to. This information includes id, index, name, status, description and entityRefs.

### Creating change sets

```python theme={null}
seal.create_change_set(entity_ids, ?change_set_name)
```

Creates a new change set containing the provided entities. Returns information about the created change set including id, index, name, status, description and entityRefs.

### Requesting review for a change set

```python theme={null}
seal.request_change_set_review(change_set_id_or_index, review_config=None, allow_direct_publish=False)
```

Requests review for a change set, or publishes it directly if no review phases are configured.

**Parameters:**

* `change_set_id_or_index`: The ID or index of the change set (found in the URL), or a change-set dict returned by `get_change_set_for_entity`.
* `review_config`: (Optional) Custom review configuration dictionary with `reviewPhases` array. Custom phases are **merged with** (not replacing) the review requirements from entities. If the same role appears in both, the higher `requiredNumApprovals` is used.
* `allow_direct_publish`: (Optional) If `True`, publishes the change set directly when no review phases exist. Defaults to `False`.

**Review Phase Properties:**
Each phase in `reviewPhases` can have the following properties:

* `requestedRoleId` (required): The role ID that should review this phase
* `requiredNumApprovals` (required): Number of approvals needed from this role
* `title` (optional): Display title for this review phase
* `notifiedRoleIds` (optional): Array of specific user role IDs to notify. If not provided, all users in the requested role group will be notified.

**Behavior:**

* Custom `review_config` phases are always **added to** existing entity review requirements (never override them)
* If any review phases exist (from entity requirements or custom config): Creates a review request
* If no review phases exist and `allow_direct_publish=True`: Publishes the change set directly
* If no review phases exist and `allow_direct_publish=False`: Raises an error

**Returns:** The updated change set data with status, entityRefs, and other details.

**Example:**

```python theme={null}
# Request review using default review requirements from entities
seal.request_change_set_review("12345")

# Add additional custom phase to entity requirements
# (custom phase is merged WITH existing requirements, not replacing them)
seal.request_change_set_review(
    "12345",
    review_config={
        "reviewPhases": [
            {
                "requestedRoleId": "quality-assurance-role-id",
                "requiredNumApprovals": 2,
                "title": "QA Review"
            }
        ]
    }
)

# Notify only specific users from a role group (instead of all users in the group)
seal.request_change_set_review(
    "12345",
    review_config={
        "reviewPhases": [
            {
                "requestedRoleId": "quality-assurance-role-id",
                "requiredNumApprovals": 1,
                "title": "QA Review",
                "notifiedRoleIds": ["specific-user-role-id-1", "specific-user-role-id-2"]
            }
        ]
    }
)

# Allow direct publish if no review phases exist
seal.request_change_set_review("12345", allow_direct_publish=True)
```

### Configuring reviewers without starting review

```python theme={null}
seal.configure_change_set_reviewers(change_set_id_or_index, review_phases)
```

Saves custom review phases to a change set without immediately starting the review process. These phases will be merged with entity review requirements when review is eventually requested.

> **Each call replaces the previously saved configuration.** Calling `configure_change_set_reviewers` overwrites any phases saved by a previous call — it does not append to them. To add phases without losing existing ones, read the current config first with `get_change_set_review_config`, append your new phases, then save the result back. Entity review requirements (from types and templates) can never be removed — the API enforces that all required roles are always present.

**Parameters:**

* `change_set_id_or_index`: The ID or index of the change set (found in the URL), or a change-set dict returned by `get_change_set_for_entity`.
* `review_phases`: Array of review phase configurations (same structure as in `review_config.reviewPhases` above)

**Use Case:** Pre-configure reviewers and notification settings, then request review later (either via automation or UI).

**Example:**

```python theme={null}
# Configure reviewers to notify only specific users
seal.configure_change_set_reviewers(
    "12345",
    review_phases=[
        {
            "requestedRoleId": "quality-assurance-role-id",
            "requiredNumApprovals": 1,
            "title": "QA Review",
            "notifiedRoleIds": ["user-role-id-1", "user-role-id-2"]
        }
    ]
)

# Later, request review (will use the configured phases + entity requirements)
seal.request_change_set_review("12345")
```

**Appending to existing configuration:**

```python theme={null}
# Read current config (includes entity requirements + any previously saved phases)
config = seal.get_change_set_review_config("12345")

# Append a new custom phase
config["reviewPhases"].append({
    "requestedRoleId": "new-reviewer-role-id",
    "requiredNumApprovals": 1,
    "title": "Additional Review",
})

# Save the combined config back
seal.configure_change_set_reviewers("12345", config["reviewPhases"])
```

### Getting the review config for a change set

```python theme={null}
seal.get_change_set_review_config(change_set_id_or_index)
```

Returns the merged review configuration for a change set. This is the same configuration that would be used when requesting review, combining entity review requirements with any additional custom phases.

**Parameters:**

* `change_set_id_or_index`: The ID or index of the change set (found in the URL), or a change-set dict returned by `get_change_set_for_entity`.

**Returns:** A dictionary containing:

* `reviewPhases`: Array of review phases, each with:
  * `requestedRoleId`: The role ID that should review this phase
  * `requiredNumApprovals`: Number of approvals needed
  * `title` (optional): Display title for this review phase
  * `notifiedRoleIds` (optional): Specific user role IDs to notify

**Example:**

```python theme={null}
# Get the review config for a change set
config = seal.get_change_set_review_config("12345")
print(f"Number of review phases: {len(config['reviewPhases'])}")

for phase in config['reviewPhases']:
    print(f"Phase: {phase.get('title', 'Untitled')}")
    print(f"  Requested role: {phase['requestedRoleId']}")
    print(f"  Required approvals: {phase['requiredNumApprovals']}")
```

### Getting tags

```python theme={null}
seal.get_tags_for_entity(entity_id)
```

Gets the tags on an entity.

```python theme={null}
seal.get_tags(tag)
```

Gets the tags on an entity the automation is embedded in. Can only be run from an automation card.

### Adding tags

```python theme={null}
seal.add_tag_to_entity(entity_id, tag)
```

Adds a tag to an entity. If no tag matching the input tag is found in the organisation, a new tag will be created.

```python theme={null}
seal.add_tag(tag)
```

Adds a tag to the entity the automation is embedded in. Can only be run from an automation card.

### Deleting tags

```python theme={null}
seal.delete_tag(tag_name)
```

Remove a tag by name from the entity within which the automation is embedded.

```python theme={null}
seal.delete_tag_from_entity(entity_id, tag_name)
```

### Sending notification emails

```python theme={null}
seal.send_email_notification(emails, subject, message, ?entity_id, ?display_system_id)
```

Queues notification emails to specified users who are members of your organisation. Emails are sent after the transaction commits and are subject to per-organisation daily limits and content validation.

Parameters:

* **emails**: list of recipient email addresses (1–100). All recipients must be existing users in your organisation.
* **subject**: plain text subject. URLs are not allowed.
* **message**: plain text message body. URLs are not allowed.
* **entity\_id (optional)**: entity to link the call-to-action to. If omitted, the button opens your organisation home in Seal.
* **display\_system\_id (optional)**: ID of a system whose URL slug should be used to build the call-to-action link instead of the entity's home system. Useful when the entity lives in one system but recipients should land on a styled view of it in a different system. When set, `entity_id` may refer to an entity in either the calling system or the display system (but not a third system). The display system must belong to the same organisation as the caller, and the caller must have access to it.

Returns:

Returns a dictionary with confirmation details for queued emails.

```python theme={null}
{"ok": True, "recipientCount": len(emails)}
```

Example:

```python theme={null}
seal.send_email_notification(["user1@seal.run"], "Maintenance", "Tonight 7pm.")
```

### Updating system configuration

```python theme={null}
seal.set_system_config(system_slug, *, homepage_entity_id=None)
```

Update configuration settings for a system. Only provided parameters will be updated; omitted parameters are left unchanged.

Parameters:

* **system\_slug**: The slug of the system to update (found in the URL)
* **homepage\_entity\_id** (keyword-only, optional): The entity ID to set as the homepage, `None` to clear it, or omit to leave unchanged

Returns:

A dictionary with the updated system configuration:

* `systemSlug`: The slug of the system
* `homepageEntityId`: The current homepage entity ID (or `None` if not set)

Example:

```python theme={null}
# Set homepage to a specific entity
config = seal.set_system_config("my-system", homepage_entity_id="123e4567-e89b-12d3-a456-426614174000")
print(config)
# {'systemSlug': 'my-system', 'homepageEntityId': '123e4567-e89b-12d3-a456-426614174000'}

# Clear the homepage
config = seal.set_system_config("my-system", homepage_entity_id=None)
print(config)
# {'systemSlug': 'my-system', 'homepageEntityId': None}
```

### Setting active versions

```python theme={null}
seal.set_active_version_in_entity(entity_id, version)
```

Set the active version of an entity. Pin the active version to a specific published version, or set to `None` to unpin and track the latest published version automatically. If the entity has review requirements, a review request will be created automatically.

```python theme={null}
seal.set_active_version(version)
```

Set the active version of the entity the automation is embedded in. If no version is provided, unpins the active version to track the latest published version automatically. If the entity has review requirements, a review request will be created automatically. Can only be run from an automation card.

#### Getting version metadata

```python theme={null}
seal.get_entity_version_info(entity_id)
```

Get version metadata for an entity. Returns a dictionary with:

* `allVersions` — list of published version strings
* `activeVersion` — currently active version string (or `None`)
* `draftExists` — whether a draft is open
* `draftStatusTag` — status tag on the open draft (or `None`)
* `pendingActivation` — `{versionNumber, publishedAt, previousActiveVersion, activationEligibleAt}` when a newer finished version is awaiting activation, else `None`. `activationEligibleAt` is the date the auto-activation cron will promote it; `None` when no delay was set.
* `activeVersionMeta` — `{publishedAt, activatedAt}` for the currently active version (or `None` when there is none).

Use this from automation scripts to detect entities sitting in pending activation and apply your own activation policy (e.g. training-completion gating, calendar-day rules).

```python theme={null}
info = seal.get_entity_version_info(entity_id)
pending = info.get("pendingActivation")
if pending:
    seal.set_active_version_in_entity(entity_id, pending["versionNumber"])
```

### Managing assignees

#### Getting assignees

```python theme={null}
seal.get_assignees(entity_id)
```

Get the current assignees for an entity. Returns a dictionary with an `assignees` array, where each item contains `id` (the user role ID) and `email` (the user's email address).

#### Setting assignees

```python theme={null}
seal.set_assignees(entity_id, assignees)
```

Set the assignees for an entity, replacing all existing assignees. The `assignees` parameter should be a list of strings where each item is either an email address or a user id.

#### Appending assignees

```python theme={null}
seal.append_assignees(entity_id, assignees)
```

Add assignees to an entity without removing existing ones. Use this for safe appending instead of `set_assignees`. The `assignees` parameter should be a list of strings where each item is either an email address or a user id.

### Managing entity-specific permissions

Entity permissions control who can manage and operate on specific entities. There are two permission type lists: **managers** and **operators**. Each permission list can contain role IDs, or the special values `"ALL_MANAGERS"` or `"ALL_OPERATORS"`.

#### Getting permissions

```python theme={null}
seal.get_permissions_for_entity(entity_id)
```

Get the current manager and operator permissions for an entity. Returns a dictionary with `managers` and `operators` arrays.

```python theme={null}
seal.get_permissions()
```

Get the current manager and operator permissions for the entity the automation is embedded in. Can only be run from an automation card.

#### Setting permissions

```python theme={null}
seal.set_permissions_for_entity(entity_id, managers=None, operators=None)
```

Replace the manager and/or operator permissions list for an entity. You can provide `managers`, `operators`, or both. Each parameter should be a list of role IDs, `"ALL_MANAGERS"`, or `"ALL_OPERATORS"`.

```python theme={null}
seal.set_permissions(managers=None, operators=None)
```

Replace the manager and/or operator permissions list for the entity the automation is embedded in. Can only be run from an automation card.

#### Adding and removing single permissions

For incremental changes — adding or removing a single role from an entity's permission list — use the idempotent helpers below. They are safe to retry, safe to call concurrently, and avoid the race conditions of a get-modify-set cycle on `set_permissions_for_entity`.

Each call writes a `GRANT_APPLIED` or `GRANT_REVOKED` audit event tagged with the calling script. No-op calls (granting a role that is already present, or revoking one that is absent) write nothing.

```python theme={null}
seal.add_operator(entity_id, role)
seal.remove_operator(entity_id, role)
seal.add_manager(entity_id, role)
seal.remove_manager(entity_id, role)
```

`role` accepts either a role ID or one of the reserved strings `"ALL_MANAGERS"` / `"ALL_OPERATORS"`.

#### Managing user group membership

Use the helpers below to add or remove a user from a custom user group (or the org Admin role) — for automating "Trained Operators"-style lists from training-record publishes, or revoking access when a credential expires. Idempotent and concurrent-safe; no-op calls write no audit event.

```python theme={null}
seal.add_user_to_role(role_id, user_id)
seal.remove_user_from_role(role_id, user_id)
```

Each membership change writes a single-element `ADD_ROLE_CHILD_RELATIONS` or `REMOVE_ROLE_CHILD_RELATIONS` audit event.

### Getting role information

```python theme={null}
seal.get_role(role_id)
```

Get information about a role by ID. Roles can represent users, API keys or user groups. Different role types have different information available, e.g. `email` for users. Role groups automatically include all user members in a `members` array.

**Getting all members of a user group:**

```python theme={null}
role = seal.get_role(role_id)

# Access the members array (only present for role groups)
if "members" in role:
    for member in role["members"]:
        print(f"{member['name']} - {member['email']}")
```

```python theme={null}
seal.get_user(user_id)
```

Get information about a user by their role ID. This is a convenience wrapper around `get_role()` that validates the role is a user. Returns user data including `id`, `name`, `email`, and `type`. Raises an exception if the role ID does not correspond to a user.

```python theme={null}
seal.get_role_group(group_id)
```

Get information about a role group by its role ID, including its members. This is a convenience wrapper around `get_role()` that validates the role is a group. Returns role group data including `id`, `name`, `type`, and a `members` array. Raises an exception if the role ID does not correspond to a role group.

```python theme={null}
seal.get_role_group_by_name(group_name)
```

Get role group information by name. Works for predefined role groups like `"Org admin"` and `"All users"`, as well as custom user groups. Automatically includes all user members in a `members` array.

**Getting the "All users" role group with members:**

```python theme={null}
# Get the role group with all its members
all_users = seal.get_role_group_by_name("All users")
role_id = all_users["id"]
```

### Getting workflow tasks

```python theme={null}
seal.get_workflow_tasks(entity_id, ?entity_version)
```

Gets all workflow tasks associated with a workflow entity. Returns a list of dictionaries, each representing a full workflow task object. Each task includes a `completedAt` timestamp if the task has been completed, or `null` if the task is not completed.

```python theme={null}
seal.get_workflow_tasks_for_containing_entity()
```

Gets all workflow tasks for the entity that an automation is embedded in. Can only be run from an automation card. Each task includes a `completedAt` timestamp if the task has been completed.

### Getting workflows that reference an entity

```python theme={null}
seal.get_workflows_referencing(entity_id, ?entity_version)
```

Gets all workflow entities that have tasks referencing the specified entity either as a template or instance. Returns an array of full workflow entities. If no entity version is provided, workflows referencing the draft entity are returned.

```python theme={null}
seal.get_containing_entity_workflows_referencing()
```

Gets all workflow entities that have tasks referencing the entity the automation is embedded in. Can only be run from an automation card. Uses the specific version of the containing entity.

### Getting change sets

```python theme={null}
seal.get_change_set_for_entity(entity_id, ?version)
seal.get_change_set_for_entity(ref={"id": "...", "version": "..."})
```

Returns information about the change set the given entity belongs to. This information includes id, index, name, status and description. By default, we find the change set for the latest draft of the entity (if it exists).

To find the change set that a specific version of an entity belongs to, include the version as a second argument. You can also pass an entity ref object instead.

```python theme={null}
seal.get_change_set_for_containing_entity()
```

Gets the change set for the entity that an automation is embedded in. Can only be run from an automation card.

### Adding fields

```python theme={null}
seal.add_field(field_name, field_type, field_value, allow_multiple, select_options, multi_line, formula_expression)
```

Add a new field to the embedded-in entity.

```python theme={null}
seal.add_field_to_entity(entity_id, field_name, field_type, field_value, allow_multiple, select_options, multi_line, formula_expression)
```

Add a new field to any entity.

```python theme={null}
seal.add_multiple_fields([
    {
        "field_name": "Temperature",
        "field_type": "NUMBER",
        "field_value": 25.5
    },
    {
        "field_name": "Notes",
        "field_type": "STRING",
        "multi_line": True
    },
    {
        "field_name": "Status",
        "field_type": "SELECT",
        "select_options": ["Pending", "Complete", "Failed"],
        "field_value": ["Pending"]
    }
])
```

Add multiple fields to the embedded-in entity in a single operation. Each field is a dictionary with:

* `field_name` (required): The name of the field
* `field_type` (required): The field type (e.g. `"NUMBER"`, `"STRING"`, `"SELECT"`, `"BOOLEAN"`, `"DATE"`)
* `field_value` (optional): The initial value for the field
* `allow_multiple` (optional): Whether multiple values are allowed
* `select_options` (optional): List of options for SELECT fields
* `multi_line` (optional): Whether to use multi-line input for STRING fields
* `formula_expression` (optional): Formula expression for FORMULA fields

```python theme={null}
seal.add_multiple_fields_to_entity(entity_id, fields_to_create)
```

Add multiple fields to any entity in a single operation. Uses the same field format as `add_multiple_fields`.

### Updating fields

Only provided arguments will be updated. If you omit `field_value` the value will not be changed, if you pass in `None` the value will be cleared.

```python theme={null}
seal.update_field(field_name, field_value, allow_multiple, select_options, multi_line, formula_expression, format, timezone, out_of_spec_expression, search_config, allow_entities_from, display_mode, coupled, create_in_new_change_set, limit_to_pinned_versions, is_live)
```

Update a field value and/or configuration in the entity the automation is embedded in. Can only be run from an automation card.

```python theme={null}
seal.update_field_in_entity(entity_id, field_name, field_value, allow_multiple, select_options, multi_line, formula_expression, format, timezone, out_of_spec_expression, search_config, allow_entities_from, display_mode, coupled, create_in_new_change_set, limit_to_pinned_versions, is_live)
```

Update a field value and/or configuration in any entity.

**Key configuration parameters:**

* `search_config`: For REFERENCE fields, controls which entities can be selected. Uses the same query config format as `seal.search_entities()` (see above).
* `allow_entities_from`: For REFERENCE fields, restricts selection to `"CHANGE_SET"` or `"ANYWHERE"`.
* `coupled`: For REFERENCE fields, when `True`, creates linked child entities.
* `create_in_new_change_set`: For coupled REFERENCE fields, creates children in separate change sets.
* `display_mode`: For REFERENCE fields, one of `"pills"`, `"table"`, or `"filePreview"`.

```python theme={null}
seal.update_multiple_fields([
    {
        "field_name": "Field 1",
        "field_value": "value"
    },
    {
        "field_name": "Field 2",
        "field_value": 123
    },
    {
        "field_name": "Field 3",
        "field_value": ["option1", "option2"]
    }
])
```

Update multiple field values in a single operation for the entity the automation is embedded in. Can only be run from an automation card.

```python theme={null}
seal.update_multiple_fields_in_entity(entity_id, [
    {
        "field_name": "Field 1",
        "field_value": "value"
    },
    {
        "field_name": "Field 2",
        "field_value": 123
    },
    {
        "field_name": "Field 3",
        "field_value": ["option1", "option2"]
    }
])
```

Update multiple field values in a single operation for any entity.

### Update entities

```python theme={null}
seal.update_entities(entities)
```

Update entities from entity-shaped blobs. Fetch entities, modify them, send them back. Each blob must have `id` and at least one property to update. Up to 100 per call.

Supports the full entity patch surface: `title`, `fields` (value, config, type), `contentPatch`, `statusTag`, `deleteFields`, `renameFields`, `addTags`, `removeTags`, `automation`, `permissions`, `assignees`.

```python theme={null}
entities = seal.search_entities(query)
changed = []
for e in entities:
    if e["fields"]["Status"]["value"] == "Pending":
        e["fields"]["Status"]["value"] = "Done"
        changed.append(e)
if changed:
    seal.update_entities(changed)
```

You can also construct update blobs directly, including field config changes:

```python theme={null}
seal.update_entities([
    {"id": entity_id, "fields": {
        "Status": {"value": "Done"},
        "Priority": {"value": "High", "config": {"selectOptions": ["Low", "Medium", "High"]}},
    }},
    {"id": other_id, "title": "New Title", "addTags": ["urgent"]},
])
```

Only send entities you actually changed — unchanged entities still write to the database.

**Concurrency safety:** `update_entities` automatically detects if another user or script modified an entity after you read it (via `metadata.lastUpdatedAt`). If a conflict is detected, the call fails with an error instead of silently overwriting the other change. Re-fetch the entities and retry if this happens.

**Returns:** A dictionary with `updated` array of entity IDs.

#### Updating page content with `contentPatch`

`contentPatch` is a list of [RFC 6902 JSON Patch](https://datatracker.ietf.org/doc/html/rfc6902) operations applied to the entity's content. Supported ops: `add`, `remove`, `replace`.

**Paths must start with `/value/pageContent` (Page entities) or `/value/scriptCode` (Script entities).** For Page entities, `pageContent` is an array of top-level block elements, so:

* `/value/pageContent/3` — index 3 in the array (0-based). `add` inserts at that index, `replace` overwrites, `remove` deletes.
* `/value/pageContent/-` — append to the end (`add` only).
* `/value/pageContent/3/children/0` — drill into a block's contents.

**Block shape.** A field block looks like:

```python theme={null}
{"id": "<new-uuid>", "type": "field", "fieldId": "<existing-field-uuid>", "children": [{"text": ""}]}
```

`id` and `children` are required on every block; `fieldId` must point to a field that already exists on the entity.

**Creating a new reference field at a specific location.** `fields` patches don't accept a custom field `id` — the server generates it. So do it in two calls: create the field, then insert the block referencing its id.

```python theme={null}
import uuid

# 1. Create the field
seal.update_entities([{
    "id": entity_id,
    "fields": {
        "Linked samples": {
            "type": "REFERENCE",
            "value": [],
            "config": {"allowMultiple": True},
        }
    },
}])

# 2. Insert a block for it after an anchor block
entity = seal.get_entity(entity_id)
new_field_id = entity["fields"]["Linked samples"]["id"]
page = entity["content"]["value"]["pageContent"]
anchor = next(i for i, b in enumerate(page) if b.get("fieldId") == "<existing-field-id>")

seal.update_entities([{
    "id": entity_id,
    "contentPatch": [{
        "op": "add",
        "path": f"/value/pageContent/{anchor + 1}",
        "value": {
            "id": str(uuid.uuid4()),
            "type": "field",
            "fieldId": new_field_id,
            "children": [{"text": ""}],
        },
    }],
}])
```

If you don't need to target a specific location, `seal.add_fields_to_page_content_in_entity(entity_id, [field_name])` creates the field and appends the block in a single call.

### Create entities

```python theme={null}
seal.create_entities(entities, template_id=..., reference=None)
```

Create entities from entity-shaped dicts matching the data model. Each entity must specify a template via `sourceInfo.template.id` (or the `template_id` convenience parameter). Up to 100 per call.

Entity-shaped format (recommended):

```python theme={null}
seal.create_entities([{
    "sourceInfo": {"template": {"id": template_id}},
    "title": "Sample A",
    "fields": {"SKU": {"value": "S-001"}},
}], reference={"entity_id": entity_id, "field_name": "Samples"})
```

Flat convenience format (fields as top-level keys):

```python theme={null}
seal.create_entities([
    {"title": "Sample A", "SKU": "S-001"},
], template_id=template_id,
   reference={"entity_id": entity_id, "field_name": "Samples"})
```

Mixed-template creation with per-row `template_id`:

```python theme={null}
seal.create_entities([
    {"title": "Step A", "template_id": prep_template_id},
    {"title": "Step B", "template_id": mix_template_id},
], reference={"entity_id": entity_id, "field_name": "Steps"})
```

**Parameters:**

* `entities` (required): list of entity-shaped dicts or flat field dicts. Entity-shaped dicts use `sourceInfo.template.id` and `fields: { name: { value } }`. Flat dicts treat non-reserved keys as field values.
* `template_id` (optional): template ID string. Used as fallback for flat dicts that don't specify their own `template_id` or `sourceInfo`.
* `reference` (optional): dict with `entity_id` and `field_name` — adds created entities to a reference field

**Returns:** A dictionary with `created` array of entity IDs.

### Deleting fields

```python theme={null}
seal.delete_field(field_name)
```

Remove a field from the embedded-in entity. Note that the field's data will also be removed.

```python theme={null}
seal.delete_field_in_entity(entity_id, field_name)
```

Delete field in any entity.

### Renaming fields

```python theme={null}
seal.rename_field(field_name, new_field_name)
```

Rename a field in the entity the automation is embedded in. Can only be run from an automation card.

```python theme={null}
seal.rename_field_in_entity(entity_id, field_name, new_field_name)
```

Rename a field in any entity.

### Adding fields to page content

```python theme={null}
seal.add_fields_to_page_content(field_names)
```

Add field cards to the page content of the entity the automation is embedded in. All field elements will be appended to the page in the order they are specified.

```python theme={null}
seal.add_fields_to_page_content_in_entity(entity_id, field_names)
```

Append field cards to the page content of any entity. All field elements will be appended to the page in the order they are specified.

### Deleting fields from page content

```python theme={null}
seal.delete_fields_from_page_content(field_names)
```

Delete all instances of these field cards from the page content of the entity the automation is embedded in.

```python theme={null}
seal.delete_fields_from_page_content_in_entity(entity_id, field_names)
```

Delete all instances of these field cards from the page content of any entity.

### Duplicating entities

```python theme={null}
seal.duplicate_entity( entity_id: str | None, version: str | None, *, ref: EntityReference | None, title: str | None, is_test: bool = False)
```

Duplicate an entity. By default, the latest version or draft of the entity is duplicated.

To duplicate a specific version of an entity, include the version as a second argument. You can also pass an entity ref object instead which will override the `entity_id` and `version` arguments.

Set `is_test=True` to create a test entity duplicate.

### Updating properties

```python theme={null}
seal.update_entity_title(entity_id, title, ?overwrite_computed_title)
```

Update the title of any entity. If `overwrite_computed_title` is set to True and the instance has a computed title, this will be overwritten.

```python theme={null}
seal.update_containing_entity_title(title, ?overwrite_computed_title)
```

Update the title of the entity the automation is embedded in. Can only be run from an automation card.

### Getting out of spec fields

```python theme={null}
seal.get_out_of_spec_fields(entity_id, ?version)
seal.get_out_of_spec_fields(ref={"id: "...", "version": "..."})
```

Gets the out of spec fields for the provided entity. By default, the latest version or draft of the entity is used.

To view the out of spec fields from a specific version of an entity, include the version as a second argument. You can also pass an entity ref object instead.

### Handling files

```python theme={null}
seal.download_file(file_entity_id, ?version)
```

The file is downloaded to the local filesystem of the script execution environment.\
The value returned is the local file path as a string.

For large files (e.g. AMBR CSV exports), use chunked reading to avoid loading the entire file into memory:

```python theme={null}
file_path = seal.download_file(file_entity_id)

# Process in chunks of 10,000 rows — works with files of any size
for chunk in pd.read_csv(file_path, chunksize=10000):
    # Process each chunk
    results = chunk.groupby("column_name").mean()
    print(results)
```

```python theme={null}
seal.upload_file(file_path, file_name, type_title, template_ref)
```

Uploads a file into a new entity. The provided type must have 'File' content type.\
`template_ref` is optional. If not provided, the file will be created from the type's default template if it exists.\
Returns the entire json blob of data of the new entity.\
When called from a script embedded in a different system, the file entity is created in the embedded entity's system.

### Creating entities from reference fields

```python theme={null}
seal.create_from_reference_field(
   "field_name",
   field_values_df=pd.DataFrame([{"Title": "Entity with data"}]),
   number_of_empty_entities=2,  # Creates 1 entity with data + 2 blank entities
   type_or_template_ref={"id": "template_id", "version": "1"}
)
```

Create entities from a reference field and automatically add them to the field value.

When `type_or_template_ref` is not provided, the entity is created based on the reference field's search configuration.

Parameters:

* `field_name`: Name of the reference field
* `field_values_df`: (Optional) DataFrame with initial field values for created entities. If a column is called 'title' or 'Title', it will set the entities' titles
* `number_of_empty_entities`: (Optional) Number of blank entities to create in addition to any entities from the DataFrame
* `type_or_template_ref`: (Optional) Entity reference object pointing to either a type or template:
  * For template references: `{"id": "template_uuid", "version": "1"}` (creates instances). The `version` field can be `None` to use the template's current active published version.
  * For type references: `{"ref": {"id": "type_uuid"}}` (creates templates)

You must provide either `field_values_df` with data, `number_of_empty_entities`, or both.

You can also create entities from a reference field in any entity:

```python theme={null}
seal.create_from_reference_field_in_entity(entity_id, ...the same)
```

### Creating templates

```python theme={null}
seal.create_template_from_type(
  type_name,
  title='title',
  initial_content_value={ ... }
)
seal.create_template_from_type(
  ref={"id": "...", "version": "..."},
  title='title',
  initial_content_value={ ... }
)
```

Create a template from a type. This allows you to programmatically create new templates that can then be used to create instances. When called from a script embedded in a different system, the template is created in the embedded entity's system.

Parameters:

* `type_name`: Name of the type to create a template from
* `title`: (Required, keyword-only) Title for the new template
* `initial_content_value`: (Optional, keyword-only) Initial content value for the template (for content-based types)

### Creating instances

```python theme={null}
seal.create_instance_from_template(
  template_id,
  ?version,
  field_values={ "field": "value", ... },
  title='title'
)
seal.create_instance_from_template(
  ref={"id": "...", "version": "..."},
  field_values={ "field": "value", ... },
  title='title'
)
```

Create an instance from a template. By default, instances are created from the latest published version of the template.

To create an instance from a specific version of a template, include the version as a second argument. You can also pass an entity ref object instead.

When a script is embedded in an entity that belongs to a different system than the template, the instance is automatically created in the embedded entity's system.

Parameters:

* `template_id`: ID of the template to create an instance from
* `version`: (Optional, keyword-only) Specific version of the template
* `field_values`: (Optional, keyword-only) Initial field values for the instance. The specified fields must exist on the template.

### Creating charts

```python theme={null}
chart = alt.Chart(data).mark_bar().encode(x="x", y="y")
chart_entity_id = seal.create_chart(chart, title="Sample Chart", type_title="Chart", template_ref={"id": "template_id", "version": "1"})
```

Generate a chart from an Altair chart. Returns the created chart `entity_id` as a string. When called from a script embedded in a different system, the chart entity is created in the embedded entity's system. See parameters below:\\

* `chart`: The Altair chart instance
* `title`:The name for the chart
* `type_title`: The name of the type to create a chart from. The content type must be `Chart.`
* `template_ref`: (Optional) Entity reference object pointing to a template. i.e `{"id": "template_uuid", "version": "1"}`. If not provided, the chart instance will be created from the type's default template if it exists.
* `creating_via_entity_id`: (Optional) The entity ID of a parent entity. When provided, the chart will be created in the parent entity's open changeset instead of a new standalone changeset.

### Running automations

```python theme={null}
seal.run_script(
  script_id,
  ?version
)
seal.run_script(
  ref={"id: "...", "version": "..."},
)
```

Run an automation entity (content type `Script code`). By default, the latest version is run.

To run a specific version, include the version as a second argument. You can also pass an entity ref object instead.

Parameters:

* `script_id`: ID of the automation to run
* `version`: (Optional, keyword-only) Specific version of the automation

```python theme={null}
seal.run_embedded_scripts(
  entity_id,
  ?version,
  ?card_ids=["..."]
)
seal.run_embedded_scripts(
  ref={"id: "...", "version": "..."},
  ?card_ids=["..."]
)
```

Parameters:

* `entity_id`: ID of the entity with embedded automations
* `version`: (Optional, keyword-only) Specific version of the entity
* `card_ids`: (Optional, keyword-only) List of specific card ids to run on the page

Run all action buttons and automation cards embedded in an entity with page content. You can optionally pass in the `card_ids` argument to specify which automations to run. These IDs can be found in the page content of the entity (see the [Entity Schema](https://backend.seal.run/api/schema.json) for more details).

### Getting live backlinks

```python theme={null}
seal.get_live_backlinks(entity_id)
```

Returns the ids of all entities whose live data includes a reference to any version of the requested entity.

### Using Neil AI

Note the ai agent must be enabled for your organisation. Contact Seal support to find out more.

```python theme={null}
seal.call_neil(user_prompt, file_entity_ids=None)
```

Execute the AI agent in the current context. If called from an embedded automation, uses the containing entity's change set. If called from a standalone automation, uses the automation entity's own change set.

Parameters:

* `user_prompt`: The prompt to send to the AI agent
* `file_entity_ids`: (Optional, keyword-only) List of file entity IDs to provide as context. Useful for passing a list of files to generate new entities from

Returns the agent's text response as a string.

```python theme={null}
seal.call_neil_in_change_set(user_prompt, change_set_index, file_entity_ids=None)
```

Execute the AI agent with a user prompt in a specific change set.

Additional Parameters:

* `change_set_index`: The index of the change set to execute the agent in, or a change-set dict returned by `get_change_set_for_entity`.

### Reviewing entities with AI

Note the AI agent must be enabled for your organisation. Contact Seal support to find out more.

```python theme={null}
seal.review_entity(additional_instructions=None)
```

Execute the review AI agent to analyze the containing entity and leave feedback comments. Can only be called from an automation embedded in an entity. The agent uses industry expertise to analyze the entity by default.

Parameters:

* `additional_instructions`: (Optional) Additional instructions to guide the review. If not provided, the agent uses its default review expertise.

Returns a summary of the review analysis as a string.

```python theme={null}
seal.review_entity_by_id(entity_id, entity_version=None, additional_instructions=None)
```

Execute the review AI agent to analyze an entity and leave feedback comments. The agent uses industry expertise to analyze the entity by default.

Parameters:

* `entity_id`: The ID of the entity to review
* `entity_version`: (Optional) Version of the entity to review. If not provided, reviews the latest version.
* `additional_instructions`: (Optional) Additional instructions to guide the review. If not provided, the agent uses its default review expertise.

Returns a summary of the review analysis as a string.

```python theme={null}
seal.review_change_set(change_set_index, additional_instructions=None)
```

Execute the review AI agent to analyze a change set and leave feedback comments. The agent uses industry expertise to analyze the change set by default.

Parameters:

* `change_set_index`: The index of the change set to review, or a change-set dict returned by `get_change_set_for_entity`.
* `additional_instructions`: (Optional) Additional instructions to guide the review. If not provided, the agent uses its default review expertise.

Returns a summary of the review analysis as a string.

### Getting trigger info

```python theme={null}
seal.get_trigger_info()
```

If the automation is being run as a trigger, this method returns an object containing context\
about the trigger run: `trigger_id` and `triggered_by_entity_id`.

## Importing packages

Seal comes with many common Python packages pre-installed. If there are other python packages you regularly require, please contact [Seal support](mailto:support@seal.run).

These packages are automatically imported in every automation, so don't need to be manually imported:

Available packages to import include:

* altair
* annotated-types
* attrs
* blinker
* cachetools
* certifi
* cffi
* charset-normalizer
* click
* cloudevents
* contourpy
* cryptography
* cycler
* deprecation
* et-xmlfile
* flask
* fonttools
* functions-framework
* gcloud
* google-api-core
* google-auth
* google-cloud-appengine-logging
* google-cloud-audit-log
* google-cloud-core
* google-cloud-error-reporting
* google-cloud-logging
* google-cloud-storage
* google-crc32c
* google-resumable-media
* googleapis-common-protos
* grpc-google-iam-v1
* grpcio
* grpcio-status
* gunicorn
* httplib2
* idna
* itsdangerous
* jinja2
* joblib
* jsonschema
* jsonschema-specifications
* jwcrypto
* kiwisolver
* lxml
* markupsafe
* matplotlib
* numpy
* oauth2client
* openpyxl
* packaging
* pandas
* pillow
* proto-plus
* protobuf
* pyasn1
* pyasn1-modules
* pycorn
* pycparser
* pycryptodome
* pydantic
* pydantic-core
* pyparsing
* pyrebase4
* python-dateutil
* python-docx
* python-jwt
* pytz
* referencing
* requests
* requests-toolbelt
* rpds-py
* rsa
* scikit-learn
* scipy
* setuptools
* six
* tabulate
* threadpoolctl
* toolz
* typing-extensions
* urllib3
* vl-convert-python
* watchdog
* werkzeug
* xlrd

To temporarily install a package when running the automation:

```python theme={null}
import subprocess
subprocess.check_output("pip install {name of package}".split())
```

## Examples

### Creating and Linking Charts

This example automation would be embedded in an entity containing a SUBMISSION field, with your chart data, and a REFERENCE field to embed the generated chart in.

```python theme={null}
import pandas as pd
import altair as alt

# Name of the chart entity to create
CHART_TITLE = "Growth Rate"
# Your type entity with content type: 'Chart'
# Find or create the type from org setting -> types
CHART_TYPE_NAME = "Chart"
# The containing entity REFERENCE field to append the generated file entity to
REFERENCE_FIELD_NAME = "Charts"
# Reference field name containing your chart data
DATA_FIELD_NAME = "Data"
# Column name to plot on the X axis
X_COLUMN = "Time"
# Column name to plot on the Y axis
Y_COLUMN = "Growth"

# Extract data from reference field
entity = seal.get_containing_entity()
data_field = entity["fields"].get(DATA_FIELD_NAME, {})
refs = data_field.get("value", [])

# Collect data from referenced entities
data = []
for ref in refs:
    ref_entity = seal.get_entity(ref=ref)
    fields = ref_entity["fields"]

    timepoint = fields.get(X_COLUMN, {}).get("value")
    growth_rate = fields.get(Y_COLUMN, {}).get("value")

    if timepoint and growth_rate:
        data.append({X_COLUMN: timepoint, Y_COLUMN: growth_rate})

# Create DataFrame and chart
df = pd.DataFrame(data)
chart = alt.Chart(df).mark_line(point=True).encode(x=X_COLUMN, y=Y_COLUMN)

# Create chart entity and link to reference field
chart_id = seal.create_chart(chart, title=CHART_TITLE, type_title=CHART_TYPE)

existing_refs = entity["fields"].get(REFERENCE_FIELD_NAME, {}).get("value", []) or []
updated_refs = existing_refs + [{"id": chart_id, "version": None}]
seal.update_field_value(REFERENCE_FIELD_NAME, updated_refs)
```

### Creating labels

Create PDF labels from entity data with configurable layouts. This method requires the Label creation blueprint to be installed. To setup this up, see [Creating labels blueprint](https://seal.run/blueprints/labels-v1).

```python theme={null}
seal.create_label_v1()
```

This automation uses the [ReportLab PDF Library](https://docs.reportlab.com/reportlab/userguide/ch1_intro/) to create labels with text, barcodes (Code128/Code39/QR), images, and geometric elements. It automatically operates in **Preview Mode** when embedded in a configuration entity (for testing) or **Trigger Mode** when triggered by entity updates (for automatic label generation). The configuration is stored in entities with layout elements defined in submission tables.

## Field structure in JSON

Fields are stored as an object called `fields`, keyed by the unique field names. Every field object has the following fields:

* `id` - a unique UUID given to every field, so it can be referenced. When a Data Record is created from a Data Step, it will have the same fields with matching `id`s
* the field's `type`
* the field's `dataType` (usually the same as the type, but multiple field types may use the same underlying data type). `type` is how it appears in the UI, `dataType` refers to the underlying data type
* `value` - the actual data. All field values are nullable - they are usually null in Data Steps (unless you want a default value for produced Records), and then populated in Data Records when a lab technician is doing data entry, for example.
* `config` - certain field have additional config, for example specifying a number display format, or whether the field can contain multiple values.

For example:

```
"fields": {
    "CSV file": {
      "id": "f1714b77-d081-4a1e-bfc4-427289fce204",
      "type": "REFERENCE",
      "value": [
        {
          "id": "e9a49732-49a0-4674-b683-c6991fac160a",
          "version": "12"
        }
      ],
      "config": {
        "allowMultiple": false
      },
      "dataType": "ENTITY"
    },
    "Group name": {
      "id": "0519bc74-7086-4ba2-b769-994b62a48945",
      "type": "STRING",
      "value": "A23-Z",
      "config": {},
      "dataType": "STRING"
    },
    "Sample weight": {
      "id": "70c33317-16ae-48b2-9907-fc5112677773",
      "type": "NUMBER",
      "value": 52.3,
      "config": {
        "format": "0.000"
      },
      "dataType": "NUMBER"
    }
  }
```

## Field types

The columns in the dataframe are converted to fields in each Record. Seal infers the field types based on the data provided:

| Data shape                                      | Field type                            |
| ----------------------------------------------- | ------------------------------------- |
| a number or `None`                              | NUMBER                                |
| a boolean                                       | BOOLEAN                               |
| an ISO date string                              | DATE                                  |
| an ISO datetime string (must be timezone aware) | DATETIME                              |
| a plain text string                             | STRING                                |
| an array of UUID strings                        | UPLOAD (i.e. an array of File ids)    |
| an array of non-UUID strings                    | SELECT (a select field aka dropdown)  |
| an array of id, version objects                 | REFERENCE (an entity reference field) |

### Expand this section to find out more about specific field type configurations

**Type: NUMBER**

**Description:** Numeric value.

**Value:** A valid JSON number or `null`.

**Config:**

```typescript theme={null}
format; // '0.X' | '0.0'| '0.00' | '0.000' | '0.0000' | 'SCI' | 'SCI3' | 'Rounded'
```

***

**Type: STRING**

**Description:** Text.

**Value:** A string or `null`. The string must be at least length 1 - to represent an empty value, use `null`.

**Config**: None

***

**Type: BOOLEAN**

**Description:** True/false.

**Value:** A JSON boolean or `null`.

**Config**: None

```
    "Checkbox": True
```

***

**Type: DATE**

**Description:** A date in ISO 8601 format.

**Value:** An ISO date string or `null`

**Config:** None

***

**Type: DATETIME**

**Description:** A datetime in the UTC timezone in ISO 8601 format.

**Value:** A timezone aware ISO datetime string with zero offset, or `null`

**Config:** `"2025-02-18T14:10:30.815Z"`

```typescript theme={null}
format // 'PPpp' | 'PPppp' | 'Pp' | "yyyy-MM-dd'T'HH':'mm':'ssXXX"Z | 'dd-MM-yyyy HH:mm:ss'

    "Time & Date": "2025-02-18T14:10:30.815Z"
```

***

**Type: SELECT**

**Description:** An array of enum-like strings. Displayed in the UI as a dropdown/select field with multiple options.

**Value:** An array of strings.

**Config:**

```typescript theme={null}
selectOptions // array of strings specifying the enum options
allowMultiple // bool, whether multiple values are allowed

    "Select": ["Option"]
```

***

**Type: REFERENCE**

**Description:** An entity reference field for referencing other entities (of any kind), including File entities for referencing files.

**Value:** An array of `{id, version}` objects.

**Config:**

```typescript theme={null}
allowMultiple // bool

    "Reference": [{ "version": '1', "id": "entity id"}]
```

## Title shortcut

If the column name is 'title' in the dataframe, Seal will automatically output this as the title of the Record, rather than creating a field called 'title'.
