Entity References in Workflows and Records
Learn how to work with entity references in Ironclad workflows and records, including different approaches for handling entity data in API responses.
Ironclad Public API: Entity References Guide
Overview
Entity references allow you to link entities (counterparties, vendors, customers, etc.) to workflows and records in Ironclad. Ironclad supports multiple entities per contract. This guide covers how to work with entity references across different API endpoints, including the multi-entity entity_references field, and the various approaches for handling entity data in API responses.
Multi-Entity Support
Records API
To support multiple entities per contract, the Records API has a new entity_references property (an array) inside addProperties. This array is the canonical multi-entity field and supersedes the existing singular entity_reference property.
Every Records API GET response carries both fields. The singular entity_reference represents the first entity in the entity_references array. For single-entity contracts, both fields describe the same entity. For multi-entity contracts, the singular shows only the first; the full list is in the array.
What changes for writes (POST / PATCH):
- If your integration sends
entity_reference(the singular), it continues to work. The server treats it as "this contract has one entity" — every write replaces the full entity list with that single entity. - If your integration sends
entity_references(the array), the server writes the full array. The singular is automatically kept in sync with the first entry so consumers still on the legacy field see the first entity. - If your integration sends both in the same payload, the plural takes precedence and the singular is ignored. This makes GET → modify → PATCH round-trip workflows safe — you don't need to strip the legacy field from your payload while migrating.
Important behavior to know about while you migrate: if your integration only writes entity_reference (the singular), every write replaces the full entity list with a single entity. If a user adds additional entities to a contract through the Ironclad UI, your next sync will overwrite them. To preserve the full list, migrate the write path to entity_references. Both fields remain available while you migrate — we recommend migrating reads and writes to entity_references to take advantage of multi-entity support.
Workflows API
Workflows can now have multiple entity fields. You can set multiple entities when launching a workflow, and update or remove them after launch during the review step.
GET /workflows/:id surfaces each entity field under its own attribute key in the attributes object of the response — not as a single entity_reference key.
For many integrations, entity_reference is already the real attribute ID of the first entity slot. For backward compatibility, entity_reference works as an alias for the first entity slot when no field is literally named entity_reference and that slot hasn't already been set by its real attribute ID. To address every entity slot reliably, pull each field's real attribute ID from the workflow schema endpoint.
Both APIs are backward compatible — existing integrations should keep working — but we recommend migrating to
entity_references(Records) and to real per-field attribute IDs (Workflows) soon.
Entity Reference Approaches
Ironclad provides two approaches for handling entity references in API responses. These references are returned by workflow and record endpoints, but not by schema endpoints (which use a different format). Both approaches apply to every entity in a multi-entity entity_references array, not just a single entity.
1. Returning Entity Reference (Minimal Information)
Description: The API returns only minimal reference information:
recordId- The unique identifier for the entitysubTypeId- The relationship type ID that defines the entity's role in the workflow or record (e.g., customer, vendor, counterparty, etc.). This ID corresponds to a relationship type that determines how the entity is used in the context. You can retrieve available relationship types using the Get Relationship Types endpoint.
Example Response:
{
"entity_reference": [
{
"type": "reference",
"value": {
"recordId": "06cb35af-bcc5-4aca-a7e8-cc4f4a1035d2",
"subTypeId": "b9369328-af3a-4120-933b-c3476cc16973"
}
}
]
}2. Hydrating Entity in API Response (Detailed Information)
Description: The API includes hydrated entity details:
readableIdattributes(e.g.,pocName,pocEmail,address)
Example Response:
{
"entity_reference": [
{
"type": "reference",
"value": {
"recordId": "8e2e25d1-37fa-4d77-804c-a865adebfe83",
"subTypeId": "b9369328-af3a-4120-933b-c3476cc16973",
"readableId": "ENTITY-1",
"attributes": {
"entity_pocName": "Jane Doe",
"entity_pocEmail": "[email protected]",
"entity_address": {
"type": "address",
"value": {
"lines": ["123 Business St"],
"locality": "San Francisco",
"region": "CA",
"postcode": "94105",
"country": "US"
}
},
"entity_category": "external",
"entity_businessType": "company"
}
}
}
]
}API Endpoints
1. Reading Entity Information from Workflows
Endpoint: GET /workflows
Required OAuth Scope: public.workflows.readWorkflows
Query Parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
hydrateEntities | boolean | No | false | Include full entity details in the response |
filter | string | No | none | Filter response by a formula |
Filtering by Entity:
You can filter workflows by entity using the filter parameter with formulas. For example, to find all workflows associated with a specific entity:
curl --request GET \
--url 'https://na1.ironcladapp.com/public/api/v1/workflows?filter=Equals([entity], "c207c133-1f21-41b0-b6df-6ae01629cb3e")' \
--header 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
--header 'accept: application/json'For more information about available formulas and filtering options, see the Formulas Help Center. The same filter parameter and entity formulas also work on the records endpoint.
Example Request:
curl --request GET \
--url 'https://na1.ironcladapp.com/public/api/v1/workflows?hydrateEntities=true' \
--header 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
--header 'accept: application/json'Response Code: 200 OK
Example Response:
Workflows will surface each entity under its own attribute key:
{
"page": 0,
"pageSize": 20,
"count": 1,
"list": [
{
"id": "681b91ce943b808e0503eeea",
"title": "Contract Agreement with Example Corporation",
"template": "68126d0c48031c7f40ad5e5a",
"step": "Sign",
"attributes": {
"readableId": "IC-5",
"counterpartyName": "Example Corporation",
"entity_reference": [
{
"recordId": "8e2e25d1-37fa-4d77-804c-a865adebfe83",
"readableId": "ENTITY-1",
"namedTypeIds": [
"b9369328-af3a-4120-933b-c3476cc16973"
],
"attributes": {
"entity_address": {
"type": "address",
"value": {
"lines": ["123 Business St"],
"locality": "San Francisco",
"region": "CA",
"postcode": "94105",
"country": "US"
}
},
"entity_pocName": "Jane Doe",
"entity_category": "external",
"entity_pocEmail": "[email protected]",
"entity_businessType": "company"
}
}
],
"localAnotherEntity": [
{
"recordId": "another-record-id",
"readableId": "ENTITY-2",
"namedTypeIds": [
"12345678-1234-1234-1234-123456789123"
],
"attributes": {
"entity_address": {
"type": "address",
"value": {
"lines": ["12345 New Street"],
"locality": "Indianapolis",
"region": "Indiana",
"postcode": "46208",
"country": "US"
}
},
"entity_pocName": "John Doe",
"entity_category": "external",
"entity_pocEmail": "[email protected]",
"entity_businessType": "company"
}
}
]
}
}
]
}Endpoint: GET /workflows/:id
Required OAuth Scope: public.workflows.readWorkflows
Query Parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
hydrateEntities | boolean | No | false | Include full entity details in the response |
2. Creating Workflows with Entity References
Endpoint: POST /workflows
Required OAuth Scope: public.workflows.createWorkflows
Entity Mapping Behavior:
When you provide an entity reference during workflow creation, the system will automatically populate any workflow fields that are mapped to entity properties. If you provide a value for a field that is mapped to an entity property, your provided value will take precedence over the entity's value.
Note: The exact mapping behavior depends on your workflow template configuration. Some fields may be automatically populated from entity data, while others may require manual input.
Setting multiple entities: set each entity field by passing its real attribute ID in attributes. You can set multiple entities in a single launch call. Pull each entity field's real attribute ID from the workflow schema endpoint (entities will have the type referenceType). The value you set for the attribute ID should be the entity's readable ID (e.g. "ENTITY-1") or its record UUID. Passing a non-string value returns a 400 Bad Request with error code INVALID_PARAM.
Backwards compatibility:
If you've used our API before, you may have seen documentation referencing a hard-coded entity_reference key as the attribute ID to use for the entity field. Now that workflows can have multiple entities, it is recommended that you always use the schema endpoint to retrieve the correct attribute ID for each entity field in your specific workflow.
For backwards compatibility, the attribute ID entity_reference is still accepted as an alias for the first entity slot on the form, even if that first entity field has a different attribute ID. The alias applies only when:
- No field is literally named
entity_reference - The first entity field hasn't already been set by its real attribute ID
If you pass both the real attribute ID and entity_reference for the same slot, the real attribute ID wins. If you pass the same attribute ID twice with different values, the last write wins.
Request Body:
{
"template": "your-template-id",
"attributes": {
"entity_reference": "ENTITY-1",
"localAnotherEntity": "ENTITY-2",
"counterpartyName": "Example Company",
"other_field": "other_value"
}
}Example Request:
curl --request POST \
--url 'https://na1.ironcladapp.com/public/api/v1/workflows' \
--header 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
--header 'Content-Type: application/json' \
--data '{
"template": "your-template-id",
"attributes": {
"entity_reference": "ENTITY-1",
"counterpartyName": "Example Corporation"
}
}'Response Code: 201 Created
Endpoint: POST /workflows/async
Required OAuth Scope: public.workflows.createWorkflows
Note: The request body format is identical to the synchronous workflow creation endpoint.
3. Updating Workflows with Entity References
Endpoint: PATCH /public/api/v1/workflows/:id/attributes
Required OAuth Scope: public.workflows.updateWorkflows
Note: The workflow must be at the Review step to update attributes. Each entity field is updated or removed independently by its own attribute path — set or remove one entity field without affecting any other entity field on the workflow.
Important: set/remove on an entity field only swaps which entity is linked; it does not re-sync attributes that are mapped from that entity. Set those explicitly in the same request if they need to change.
Setting an Entity Reference:
{
"updates": [
{
"action": "set",
"path": "entity_reference",
"value": "ENTITY-1"
}
]
}Setting Multiple Entity References:
Update more than one entity field in the same request by including an update for each field's attribute path:
{
"updates": [
{
"action": "set",
"path": "entity_reference",
"value": "ENTITY-2"
},
{
"action": "set",
"path": "localAnotherEntity",
"value": "ENTITY-1"
}
]
}Removing an Entity Reference:
{
"updates": [
{
"action": "remove",
"path": "entity_reference"
}
]
}Response Code: 200 OK
Example Response:
{
"id": "workflow-id",
"title": "Updated Workflow Title",
"step": "Review",
"attributes": {
"counterpartyName": "Example Corporation"
}
}Example Request:
curl --request PATCH \
--url 'https://na1.ironcladapp.com/public/api/v1/workflows/:id/attributes' \
--header 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
--header 'Content-Type: application/json' \
--data '{
"updates": [
{
"action": "set",
"path": "entity_reference",
"value": "ENTITY-1"
}
]
}'Response Code: 200 OK
Example Response:
Note: This is an excerpt from the full response.
{
"id": "689cd5acfacbcd36740bea21",
"title": "Contract with Company Solutions Inc.",
"template": "68126d0c48031c7f40ad5e5a",
"step": "Review",
"attributes": {
"entity_reference": [
{
"recordId": "6d5637d5-4534-4b79-920f-c0d51ff93222",
"subTypeId": "ba53e018-f0ce-4892-868b-0744d7ea96cd"
}
],
...
}
...
}
4. Reading Workflow Schemas with Entity Information
Endpoint: GET /public/api/v1/workflow-schemas
Required OAuth Scope: public.workflows.readSchemas
Example Request:
curl --request GET \
--url 'https://na1.ironcladapp.com/public/api/v1/workflow-schemas' \
--header 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
--header 'accept: application/json'Endpoint: GET /public/api/v1/workflow-schemas/:templateId
Required OAuth Scope: public.workflows.readSchemas
Example Request:
curl --request GET \
--url 'https://na1.ironcladapp.com/public/api/v1/workflow-schemas/your-template-id' \
--header 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
--header 'accept: application/json'Response Code: 200 OK
Example Response:
A workflow with multiple entity fields lists each one as its own schema entry, keyed by its real attribute ID with the type referenceType:
{
"id": "your-template-id",
"name": "Standard Agreement Template",
"schema": {
"entity_reference": {
"type": "referenceType",
"displayName": "Counterparty",
"propertyKey": "entity_reference"
},
"localAnotherEntity": {
"type": "referenceType",
"displayName": "Vendor",
"propertyKey": "localAnotherEntity"
},
"counterpartyName": {
"type": "string",
"displayName": "Counterparty Name",
"propertyKey": "counterpartyName"
},
"agreementDate": {
"type": "date",
"displayName": "Agreement Date",
"propertyKey": "agreementDate"
}
}
}5. Reading Entity Information from Records
Endpoint: GET /public/api/v1/records
Required OAuth Scope: public.records.readRecords
Query Parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
hydrateEntities | boolean | No | false | Include full entity details in the response |
filter | string | No | none | Filter response by a formula |
Filtering by Entity:
You can filter records by entity using the filter parameter with formulas. For example, to find all records associated with a specific entity:
curl --request GET \
--url 'https://na1.ironcladapp.com/public/api/v1/records?filter=Equals([entity], "c207c133-1f21-41b0-b6df-6ae01629cb3e")' \
--header 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
--header 'accept: application/json'For more information about available formulas and filtering options, see the Formulas Help Center. Entity formulas match provided entity IDs against all entities in a record's entity_references list, not just the first entity.
Example Request:
curl --request GET \
--url 'https://na1.ironcladapp.com/public/api/v1/records?hydrateEntities=true' \
--header 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
--header 'accept: application/json'Response Code: 200 OK
Example Response:
Every response carries both entity_reference (the first entity) and entity_references (the full array):
{
"page": 0,
"pageSize": 20,
"count": 1,
"list": [
{
"id": "record-id",
"name": "Sample Contract Record",
"type": "contract",
"addProperties": {
"entity_reference": {
"type": "reference",
"value": {
"recordId": "8e2e25d1-37fa-4d77-804c-a865adebfe83",
"readableId": "ENTITY-1",
"attributes": {
"entity_pocName": "Jane Doe",
"entity_pocEmail": "[email protected]",
"entity_category": "external",
"entity_businessType": "company"
}
}
},
"entity_references": {
"type": "array",
"value": [
{
"recordId": "8e2e25d1-37fa-4d77-804c-a865adebfe83",
"readableId": "ENTITY-1",
"attributes": {
"entity_pocName": "Jane Doe",
"entity_pocEmail": "[email protected]",
"entity_category": "external",
"entity_businessType": "company"
}
},
{
"recordId": "3f1e25d1-37fa-4d77-804c-a865adebfe99",
"readableId": "ENTITY-2"
}
]
},
"counterpartyName": {
"type": "string",
"value": "Example Corporation"
}
}
}
]
}Endpoint: GET /public/api/v1/records/:id
Required OAuth Scope: public.records.readRecords
Query Parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
hydrateEntities | boolean | No | false | Include full entity details in the response |
6. Creating Records with Entity References
Endpoint: POST /records
Required OAuth Scope: public.records.createRecords
To create a record with a single entity, send the singular entity_reference. To create a record with multiple entities, send the plural entity_references array instead — see Multi-Entity Support for precedence rules if you send both.
Request Body (single entity):
{
"type": "contract",
"name": "My API Generated Record",
"addProperties": {
"entity_reference": {
"type": "reference",
"value": {
"recordId": "ENTITY-1"
}
},
"counterpartyName": {
"type": "string",
"value": "Example Company"
},
"agreementDate": {
"type": "date",
"value": "2024-01-15"
}
}
}Request Body (multiple entities):
{
"type": "contract",
"name": "My API Generated Record",
"addProperties": {
"entity_references": {
"type": "array",
"value": [
{ "recordId": "ENTITY-1" },
{ "recordId": "ENTITY-2" }
]
},
"counterpartyName": {
"type": "string",
"value": "Example Company"
},
"agreementDate": {
"type": "date",
"value": "2024-01-15"
}
}
}Example Request:
curl --request POST \
--url 'https://na1.ironcladapp.com/public/api/v1/records' \
--header 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
--header 'Content-Type: application/json' \
--data '{
"type": "contract",
"name": "Sample Contract Record",
"addProperties": {
"entity_references": {
"type": "array",
"value": [
{ "recordId": "ENTITY-1" },
{ "recordId": "ENTITY-2" }
]
},
"counterpartyName": {
"type": "string",
"value": "Example Corporation"
},
"agreementDate": {
"type": "date",
"value": "2024-01-15"
}
}
}'Response Code: 201 Created
7. Updating Records with Entity References
Endpoint: PATCH /public/api/v1/records/:id
Required OAuth Scope: public.records.updateRecords
Important: if this request sends only the singular entity_reference, the write replaces the full entity list with that one entity — any additional entities added through the Ironclad UI since your last sync will be overwritten. To update a record without losing other entities, GET the record first, modify the full entity_references array, then PATCH with the plural field. If both fields are present in the same payload, entity_references wins and entity_reference is ignored, which makes this GET → modify → PATCH round trip safe without needing to strip the legacy field.
Example Request (safe round trip, plural field):
curl --request PATCH \
--url 'https://na1.ironcladapp.com/public/api/v1/records/:id' \
--header 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
--header 'Content-Type: application/json' \
--data '{
"addProperties": {
"entity_references": {
"type": "array",
"value": [
{ "recordId": "ENTITY-1" },
{ "recordId": "ENTITY-2" }
]
}
}
}'Example Request (single entity, legacy field):
curl --request PATCH \
--url 'https://na1.ironcladapp.com/public/api/v1/records/:id' \
--header 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
--header 'Content-Type: application/json' \
--data '{
"addProperties": {
"entity_reference": {
"type": "reference",
"value": {
"recordId": "ENTITY-1"
}
}
}
}'Response Code: 200 OK
Entity Reference Schema
In workflow schemas, entity reference fields appear as:
{
"entity_reference": {
"type": "referenceType",
"displayName": "Counterparty",
"propertyKey": "entity_reference"
}
}A workflow with multiple entity fields has one schema entry per entity field, each with its own propertyKey (its real attribute ID) — see the multi-entity schema example above.
Error Handling
When working with entity references, you may encounter the following specific errors:
Entity-Specific Error Codes
| Code | Description |
|---|---|
INVALID_ENTITY_REFERENCE | The specified entity does not exist or is not accessible |
FORBIDDEN | You do not have permission to access the specified entity |
INVALID_PARAM | The entity reference format is invalid or the entity is archived |
NOT_FOUND | Entity with that reference could not be found |
Error Response Format
{
"code": "INVALID_ENTITY_REFERENCE",
"message": "Entity with ID ENTITY-1 not found or not accessible",
"param": "entity_reference"
}Entity Reference Validation
When working with entity references, the following validation rules apply:
- Entity Existence: The referenced entity must exist in your Ironclad workspace
- Permission Requirements:
- Read operations: The requesting user must have read access to the referenced entity
- Write operations: The requesting user must have write access to both the workflow/record and the referenced entity
- Reference Format: Entity references can be provided as:
- Entity Ironclad ID (e.g.,
ENTITY-1) - Entity record ID (UUID format)
- Entity Ironclad ID (e.g.,
- Attribute Type: When launching or updating a workflow, an entity attribute must be passed as a string (the entity's readable ID or record ID). Passing a non-string value (e.g., a number or object) now returns a validation error instead of failing silently.
Related Resources
- Entities CRUD API Guide - Complete entity management
- Getting Started with Ironclad API - API fundamentals
- Authentication Guide - API authentication
- Rate Limits - API usage limits
- Entities Overview Guide
- Entities and Relationship Types Guide
- Other Entities Articles
Updated 14 days ago

