Metadata API Examples¶
This document provides practical examples of using the unified metadata fields API in OpenContracts.
Important Note on Field Naming: OpenContracts uses Graphene Django which automatically converts Python snake_case
field names to GraphQL camelCase
. Throughout this document: - data_type
(model field) appears as dataType
(GraphQL field) - validation_config
(model field) appears as validationConfig
(GraphQL field) - is_manual_entry
(model field) appears as isManualEntry
(GraphQL field) - default_value
(model field) appears as defaultValue
(GraphQL field)
Architecture Overview¶
OpenContracts uses a unified data model for both extracted data and manual metadata entry:
- Columns define the schema for metadata fields within a corpus
- Datacells store the actual metadata values for each document
- Fieldsets organize columns and are automatically created for each corpus
- Validation is performed based on data type and validation rules
This unified approach eliminates code duplication and provides consistent handling of structured data throughout the application.
Key Concepts¶
- Corpus-Level Schema: Metadata columns are defined at the corpus level, not in labelsets
- Manual Entry Flag: Columns have an
isManualEntry
flag to distinguish metadata from extracted data - Unified Permissions: Metadata access is controlled by corpus permissions
- Data Types: Support for STRING, TEXT, BOOLEAN, INTEGER, FLOAT, DATE, DATETIME, URL, EMAIL, CHOICE, MULTI_CHOICE, and JSON
Complete Workflow Examples¶
1. Setting Up a Contract Management Corpus¶
This example shows how to create a complete metadata schema for managing contracts.
# Step 1: Create the corpus (fieldset is automatically created)
mutation CreateContractCorpus {
createCorpus(
title: "2024 Vendor Contracts"
description: "All vendor contracts for fiscal year 2024"
) {
ok
obj {
id
title
}
}
}
# Step 2: Add metadata columns to the corpus
# Assuming corpusId: "Q29ycHVzVHlwZTo3ODk="
# Add contract type field (choice)
mutation AddContractType {
createMetadataColumn(
corpusId: "Q29ycHVzVHlwZTo3ODk=",
name: "Contract Type",
dataType: "CHOICE",
helpText: "Type of vendor contract",
validationConfig: {
required: true,
choices: ["Service Agreement", "Purchase Order", "NDA", "License Agreement", "Other"],
default_value: "Service Agreement"
}
) {
ok
obj {
id
name
dataType
isManualEntry
}
}
}
# Add vendor name field (string with validation)
mutation AddVendorName {
createMetadataColumn(
corpusId: "Q29ycHVzVHlwZTo3ODk=",
name: "Vendor Name",
dataType: "STRING",
helpText: "Legal name of the vendor/supplier",
validationConfig: {
required: true,
min_length: 2,
max_length: 200
}
) {
ok
obj {
id
name
}
}
}
# Add contract value field (float with range)
mutation AddContractValue {
createMetadataColumn(
corpusId: "Q29ycHVzVHlwZTo3ODk=",
name: "Contract Value",
dataType: "FLOAT",
helpText: "Total contract value in USD",
validationConfig: {
required: true,
min_value: 0,
max_value: 10000000
}
) {
ok
obj {
id
name
}
}
}
# Add start date field
mutation AddStartDate {
createMetadataColumn(
corpusId: "Q29ycHVzVHlwZTo3ODk=",
name: "Start Date",
dataType: "DATE",
helpText: "Contract effective start date",
validationConfig: {
required: true
}
) {
ok
obj {
id
name
}
}
}
# Add end date field (optional)
mutation AddEndDate {
createMetadataColumn(
corpusId: "Q29ycHVzVHlwZTo3ODk=",
name: "End Date",
dataType: "DATE",
helpText: "Contract expiration date (leave blank for perpetual contracts)",
validationConfig: {
required: false
}
) {
ok
obj {
id
name
}
}
}
# Add auto-renewal field (boolean)
mutation AddAutoRenewal {
createMetadataColumn(
corpusId: "Q29ycHVzVHlwZTo3ODk=",
name: "Auto-Renewal",
dataType: "BOOLEAN",
helpText: "Does this contract automatically renew?",
validationConfig: {
required: true,
default_value: false
}
) {
ok
obj {
id
name
}
}
}
# Add departments field (multi-choice)
mutation AddDepartments {
createMetadataColumn(
corpusId: "Q29ycHVzVHlwZTo3ODk=",
name: "Departments",
dataType: "MULTI_CHOICE",
helpText: "Departments affected by this contract",
validationConfig: {
required: true,
choices: ["IT", "HR", "Finance", "Operations", "Legal", "Marketing", "Sales"]
}
) {
ok
obj {
id
name
}
}
}
# Add contract status field
mutation AddContractStatus {
createMetadataColumn(
corpusId: "Q29ycHVzVHlwZTo3ODk=",
name: "Status",
dataType: "CHOICE",
helpText: "Current status in the contract lifecycle",
validationConfig: {
required: true,
choices: ["Draft", "Under Review", "Approved", "Active", "Expired", "Terminated"],
default_value: "Draft"
}
) {
ok
obj {
id
name
}
}
}
# Add notes field (text)
mutation AddNotes {
createMetadataColumn(
corpusId: "Q29ycHVzVHlwZTo3ODk=",
name: "Notes",
dataType: "TEXT",
helpText: "Additional notes or comments about this contract",
validationConfig: {
required: false,
max_length: 5000
}
) {
ok
obj {
id
name
}
}
}
2. Setting Metadata Values for a Document¶
# After uploading a contract document, set its metadata values
# Assume documentId: "RG9jdW1lbnRUeXBlOjQ1Ng=="
# Column IDs from the mutations above
# Set contract type
mutation SetContractType {
setMetadataValue(
documentId: "RG9jdW1lbnRUeXBlOjQ1Ng==",
corpusId: "Q29ycHVzVHlwZTo3ODk=",
columnId: "Q29sdW1uVHlwZToxMDE=",
value: "Service Agreement"
) {
ok
message
obj {
id
data
}
}
}
# Set vendor name
mutation SetVendorName {
setMetadataValue(
documentId: "RG9jdW1lbnRUeXBlOjQ1Ng==",
corpusId: "Q29ycHVzVHlwZTo3ODk=",
columnId: "Q29sdW1uVHlwZToxMDI=",
value: "Acme Software Solutions Inc."
) {
ok
message
obj {
id
data
}
}
}
# Set contract value
mutation SetContractValue {
setMetadataValue(
documentId: "RG9jdW1lbnRUeXBlOjQ1Ng==",
corpusId: "Q29ycHVzVHlwZTo3ODk=",
columnId: "Q29sdW1uVHlwZToxMDM=",
value: 75000.00
) {
ok
message
obj {
id
data
}
}
}
# Set multiple values in one mutation
mutation SetContractDates {
startDate: setMetadataValue(
documentId: "RG9jdW1lbnRUeXBlOjQ1Ng==",
columnId: "Q29sdW1uVHlwZToxMDQ=",
value: "2024-01-01"
) {
ok
obj { id }
}
endDate: setMetadataValue(
documentId: "RG9jdW1lbnRUeXBlOjQ1Ng==",
columnId: "Q29sdW1uVHlwZToxMDU=",
value: "2024-12-31"
) {
ok
obj { id }
}
}
# Set boolean value
mutation SetAutoRenewal {
setMetadataValue(
documentId: "RG9jdW1lbnRUeXBlOjQ1Ng==",
columnId: "Q29sdW1uVHlwZToxMDY=",
value: true
) {
ok
obj {
id
data
}
}
}
# Set multi-choice value (array)
mutation SetDepartments {
setMetadataValue(
documentId: "RG9jdW1lbnRUeXBlOjQ1Ng==",
columnId: "Q29sdW1uVHlwZToxMDc=",
value: ["IT", "Operations"]
) {
ok
obj {
id
data
}
}
}
# Set status
mutation SetStatus {
setMetadataValue(
documentId: "RG9jdW1lbnRUeXBlOjQ1Ng==",
columnId: "Q29sdW1uVHlwZToxMDg=",
value: "Active"
) {
ok
obj {
id
data
}
}
}
3. Querying Metadata Schema and Values¶
# Get all metadata columns for a corpus (schema)
query GetCorpusMetadataSchema {
corpusMetadataColumns(corpusId: "Q29ycHVzVHlwZTo3ODk=") {
id
name
dataType
helpText
validationConfig
defaultValue
displayOrder
isManualEntry
outputType
taskName
}
}
# Get all metadata values for a specific document
query GetDocumentMetadata {
documentMetadataDatacells(
documentId: "RG9jdW1lbnRUeXBlOjQ1Ng==",
corpusId: "Q29ycHVzVHlwZTo3ODk="
) {
id
data
dataDefinition
column {
id
name
dataType
helpText
validationConfig
isManualEntry
}
creator {
id
email
}
}
}
# Check metadata completion status
query CheckMetadataCompletion {
metadataCompletionStatusV2(
documentId: "RG9jdW1lbnRUeXBlOjQ1Ng==",
corpusId: "Q29ycHVzVHlwZTo3ODk="
) {
totalFields
filledFields
missingFields
percentage
missingRequired
}
}
4. Updating and Managing Metadata¶
# Update an existing metadata column
mutation UpdateContractTypeChoices {
updateMetadataColumn(
columnId: "Q29sdW1uVHlwZToxMDE=",
name: "Contract Type",
validationConfig: {
required: true,
choices: ["Service Agreement", "Purchase Order", "NDA", "License Agreement", "Consulting Agreement", "Other"],
default_value: "Service Agreement"
}
) {
ok
obj {
id
name
validationConfig
}
}
}
# Delete a metadata value
mutation RemoveMetadataValue {
deleteMetadataValue(
documentId: "RG9jdW1lbnRUeXBlOjQ1Ng==",
corpusId: "Q29ycHVzVHlwZTo3ODk=",
columnId: "Q29sdW1uVHlwZToxMDE="
) {
ok
message
}
}
# Update an existing metadata value
mutation UpdateContractValue {
setMetadataValue(
documentId: "RG9jdW1lbnRUeXBlOjQ1Ng==",
columnId: "Q29sdW1uVHlwZToxMDM=",
value: 85000.00
) {
ok
obj {
id
data
}
}
}
5. Advanced Use Cases¶
Email Field with Validation¶
mutation AddContactEmail {
createMetadataColumn(
corpusId: "Q29ycHVzVHlwZTo3ODk=",
name: "Contact Email",
dataType: "EMAIL",
helpText: "Primary contact email for this contract",
validationConfig: {
required: true
}
) {
ok
obj {
id
name
}
}
}
URL Field¶
mutation AddContractURL {
createMetadataColumn(
corpusId: "Q29ycHVzVHlwZTo3ODk=",
name: "Contract Portal URL",
dataType: "URL",
helpText: "Link to vendor's contract management portal",
validationConfig: {
required: false
}
) {
ok
obj {
id
name
}
}
}
Custom Validation with Regex¶
mutation AddContractNumber {
createMetadataColumn(
corpusId: "Q29ycHVzVHlwZTo3ODk=",
name: "Contract Number",
dataType: "STRING",
helpText: "Unique contract identifier (Format: XX-YYYY-NNNNNN)",
validationConfig: {
required: true,
regex_pattern: "^[A-Z]{2}-\\d{4}-\\d{6}$"
}
) {
ok
obj {
id
name
}
}
}
Complex JSON Metadata¶
mutation AddRenewalTerms {
createMetadataColumn(
corpusId: "Q29ycHVzVHlwZTo3ODk=",
name: "Renewal Terms",
dataType: "JSON",
helpText: "Detailed renewal terms and conditions",
validationConfig: {
required: false
}
) {
ok
obj {
id
name
}
}
}
# Set complex JSON data
mutation SetRenewalTerms {
setMetadataValue(
documentId: "RG9jdW1lbnRUeXBlOjQ1Ng==",
columnId: "Q29sdW1uVHlwZToxMTA=",
value: {
"notice_period_days": 90,
"automatic_renewal": true,
"renewal_term_months": 12,
"price_adjustment": {
"type": "percentage",
"value": 3,
"basis": "CPI"
},
"opt_out_window": {
"start_days_before": 120,
"end_days_before": 90
}
}
) {
ok
obj {
id
data
}
}
}
Date and DateTime Fields¶
mutation AddSignatureDate {
createMetadataColumn(
corpusId: "Q29ycHVzVHlwZTo3ODk=",
name: "Signature Date",
dataType: "DATE",
helpText: "Date when contract was signed",
validationConfig: {
required: true
}
) {
ok
obj { id }
}
}
mutation AddLastReviewTimestamp {
createMetadataColumn(
corpusId: "Q29ycHVzVHlwZTo3ODk=",
name: "Last Review",
dataType: "DATETIME",
helpText: "Timestamp of last contract review",
validationConfig: {
required: false
}
) {
ok
obj { id }
}
}
Bulk Operations¶
# Bulk update multiple documents' status
mutation BulkUpdateStatus {
doc1: setMetadataValue(
documentId: "RG9jdW1lbnRUeXBlOjQ1Ng==",
columnId: "Q29sdW1uVHlwZToxMDg=",
value: "Expired"
) { ok }
doc2: setMetadataValue(
documentId: "RG9jdW1lbnRUeXBlOjQ1Nw==",
columnId: "Q29sdW1uVHlwZToxMDg=",
value: "Expired"
) { ok }
doc3: setMetadataValue(
documentId: "RG9jdW1lbnRUeXBlOjQ1OA==",
columnId: "Q29sdW1uVHlwZToxMDg=",
value: "Expired"
) { ok }
}
Error Handling Examples¶
Validation Errors¶
# Example: Invalid email format
mutation SetInvalidEmail {
setMetadataValue(
documentId: "RG9jdW1lbnRUeXBlOjQ1Ng==",
columnId: "Q29sdW1uVHlwZToyMDE=",
value: "not-an-email"
) {
ok
message # Will contain validation error details
}
}
# Example: Value out of range
mutation SetInvalidValue {
setMetadataValue(
documentId: "RG9jdW1lbnRUeXBlOjQ1Ng==",
columnId: "Q29sdW1uVHlwZToxMDM=",
value: -1000
) {
ok
message # Will contain: "Value must be at least 0"
}
}
# Example: Invalid choice
mutation SetInvalidChoice {
setMetadataValue(
documentId: "RG9jdW1lbnRUeXBlOjQ1Ng==",
columnId: "Q29sdW1uVHlwZToxMDE=",
value: "Invalid Type"
) {
ok
message # Will contain available choices
}
}
# Example: Required field missing
mutation SetEmptyRequired {
setMetadataValue(
documentId: "RG9jdW1lbnRUeXBlOjQ1Ng==",
columnId: "Q29sdW1uVHlwZToxMDI=",
value: ""
) {
ok
message # Will contain: "This field is required"
}
}
Integration Patterns¶
React Component Example¶
import { useQuery, useMutation } from '@apollo/client';
import { gql } from '@apollo/client';
const GET_CORPUS_METADATA_COLUMNS = gql`
query GetCorpusMetadataColumns($corpusId: ID!) {
corpusMetadataColumns(corpusId: $corpusId) {
id
name
dataType
helpText
validationConfig
defaultValue
isManualEntry
}
}
`;
const GET_DOCUMENT_METADATA_DATACELLS = gql`
query GetDocumentMetadataDatacells($documentId: ID!, $corpusId: ID!) {
documentMetadataDatacells(documentId: $documentId, corpusId: $corpusId) {
id
data
column {
id
name
dataType
validationConfig
}
}
}
`;
const SET_METADATA_VALUE = gql`
mutation SetMetadataValue($documentId: ID!, $corpusId: ID!, $columnId: ID!, $value: GenericScalar!) {
setMetadataValue(documentId: $documentId, corpusId: $corpusId, columnId: $columnId, value: $value) {
ok
message
obj {
id
data
}
}
}
`;
interface MetadataFormProps {
documentId: string;
corpusId: string;
}
export const MetadataForm: React.FC<MetadataFormProps> = ({ documentId, corpusId }) => {
// Fetch metadata schema
const { data: schemaData } = useQuery(GET_CORPUS_METADATA_COLUMNS, {
variables: { corpusId }
});
// Fetch current values
const { data: valuesData } = useQuery(GET_DOCUMENT_METADATA_DATACELLS, {
variables: { documentId, corpusId }
});
// Update mutation
const [setMetadataValue] = useMutation(SET_METADATA_VALUE);
const handleMetadataChange = async (columnId: string, value: any) => {
try {
const result = await setMetadataValue({
variables: {
documentId,
corpusId,
columnId,
value
}
});
if (!result.data.setMetadataValue.ok) {
toast.error(result.data.setMetadataValue.message);
} else {
toast.success('Metadata updated successfully');
}
} catch (error) {
toast.error('Failed to update metadata');
console.error(error);
}
};
const columns = schemaData?.corpusMetadataColumns || [];
const values = valuesData?.documentMetadataDatacells || [];
// Create a map of column ID to current value
const valueMap = new Map(
values.map(datacell => [datacell.column.id, datacell.data])
);
return (
<div className="metadata-form">
{columns.map(column => (
<MetadataField
key={column.id}
column={column}
value={valueMap.get(column.id)}
onChange={(value) => handleMetadataChange(column.id, value)}
/>
))}
</div>
);
};
Python Script Example¶
import requests
import json
from typing import Dict, List, Any, Optional
class OpenContractsMetadataClient:
def __init__(self, api_url: str, auth_token: str):
self.api_url = api_url
self.headers = {
'Authorization': f'Bearer {auth_token}',
'Content-Type': 'application/json'
}
def create_metadata_column(self, corpus_id: str, name: str, data_type: str,
validation_config: Optional[Dict] = None,
help_text: Optional[str] = None) -> Dict:
"""Create a new metadata column in a corpus."""
query = """
mutation CreateMetadataColumn(
$corpusId: ID!,
$name: String!,
$dataType: String!,
$validationConfig: GenericScalar,
$helpText: String
) {
createMetadataColumn(
corpusId: $corpusId,
name: $name,
dataType: $dataType,
validationConfig: $validationConfig,
helpText: $helpText
) {
ok
message
obj {
id
name
dataType
isManualEntry
}
}
}
"""
variables = {
'corpusId': corpus_id,
'name': name,
'dataType': data_type
}
if validation_config:
variables['validationConfig'] = validation_config
if help_text:
variables['helpText'] = help_text
response = requests.post(
self.api_url,
headers=self.headers,
json={'query': query, 'variables': variables}
)
return response.json()
def set_metadata_value(self, document_id: str, corpus_id: str, column_id: str, value: Any) -> Dict:
"""Set a metadata value for a document."""
query = """
mutation SetMetadataValue($documentId: ID!, $corpusId: ID!, $columnId: ID!, $value: GenericScalar!) {
setMetadataValue(documentId: $documentId, corpusId: $corpusId, columnId: $columnId, value: $value) {
ok
message
obj {
id
data
}
}
}
"""
response = requests.post(
self.api_url,
headers=self.headers,
json={
'query': query,
'variables': {
'documentId': document_id,
'corpusId': corpus_id,
'columnId': column_id,
'value': value
}
}
)
return response.json()
def get_corpus_metadata_schema(self, corpus_id: str) -> List[Dict]:
"""Get all metadata columns for a corpus."""
query = """
query GetCorpusMetadataColumns($corpusId: ID!) {
corpusMetadataColumns(corpusId: $corpusId) {
id
name
dataType
helpText
validationConfig
defaultValue
isManualEntry
}
}
"""
response = requests.post(
self.api_url,
headers=self.headers,
json={
'query': query,
'variables': {'corpusId': corpus_id}
}
)
result = response.json()
return result.get('data', {}).get('corpusMetadataColumns', [])
def get_document_metadata(self, document_id: str, corpus_id: str) -> List[Dict]:
"""Get all metadata values for a document."""
query = """
query GetDocumentMetadataDatacells($documentId: ID!, $corpusId: ID!) {
documentMetadataDatacells(documentId: $documentId, corpusId: $corpusId) {
id
data
column {
id
name
dataType
validationConfig
}
}
}
"""
response = requests.post(
self.api_url,
headers=self.headers,
json={
'query': query,
'variables': {
'documentId': document_id,
'corpusId': corpus_id
}
}
)
result = response.json()
return result.get('data', {}).get('documentMetadataDatacells', [])
def bulk_set_metadata_values(self, updates: List[Dict]) -> Dict:
"""Set metadata values for multiple documents."""
operations = []
for i, update in enumerate(updates):
operations.append(f"""
update{i}: setMetadataValue(
documentId: "{update['documentId']}",
columnId: "{update['columnId']}",
value: {json.dumps(update['value'])}
) {{
ok
message
}}
""")
query = f"mutation BulkUpdate {{ {' '.join(operations)} }}"
response = requests.post(
self.api_url,
headers=self.headers,
json={'query': query}
)
return response.json()
# Usage example
if __name__ == "__main__":
client = OpenContractsMetadataClient(
api_url="https://your-opencontracts-instance.com/graphql/",
auth_token="your-auth-token"
)
# Create a contract type field
result = client.create_metadata_column(
corpus_id="Q29ycHVzVHlwZTo3ODk=",
name="Contract Type",
data_type="CHOICE",
validation_config={
"required": True,
"choices": ["Service Agreement", "Purchase Order", "NDA"],
"default_value": "Service Agreement"
},
help_text="Type of vendor contract"
)
if result.get('data', {}).get('createMetadataColumn', {}).get('ok'):
column_id = result['data']['createMetadataColumn']['obj']['id']
print(f"Created column with ID: {column_id}")
# Set a value for this field
value_result = client.set_metadata_value(
document_id="RG9jdW1lbnRUeXBlOjQ1Ng==",
corpus_id="Q29ycHVzVHlwZTo3ODk=",
column_id=column_id,
value="Service Agreement"
)
if value_result.get('data', {}).get('setMetadataValue', {}).get('ok'):
print("Successfully set metadata value")
else:
print("Failed to set value:", value_result)
else:
print("Failed to create column:", result)
Data Type Reference¶
Available Data Types¶
Type | Description | Example Values | Validation Options |
---|---|---|---|
STRING | Short text | "Acme Corp" | min_length , max_length , regex_pattern |
TEXT | Long text | "Contract notes..." | min_length , max_length |
BOOLEAN | True/false | true , false | default_value |
INTEGER | Whole numbers | 42 , 1000 | min_value , max_value |
FLOAT | Decimal numbers | 1234.56 , 0.5 | min_value , max_value |
DATE | Date only | "2024-01-15" | None |
DATETIME | Date and time | "2024-01-15T10:30:00Z" | None |
URL | Web addresses | "https://example.com" | None |
EMAIL | Email addresses | "user@example.com" | None |
CHOICE | Single selection | "Option A" | choices , default_value |
MULTI_CHOICE | Multiple selections | ["Option A", "Option B"] | choices |
JSON | Complex data | {"key": "value"} | None |
Validation Configuration Options¶
interface ValidationConfig {
required?: boolean; // Field is required
default_value?: any; // Default value
min_length?: number; // Minimum string length
max_length?: number; // Maximum string length
min_value?: number; // Minimum numeric value
max_value?: number; // Maximum numeric value
regex_pattern?: string; // Regular expression pattern
choices?: string[]; // Available choices for CHOICE/MULTI_CHOICE
help_text?: string; // Additional help text
}
Best Practices¶
1. Schema Design¶
- Use descriptive names: Make column names clear and unambiguous
- Set appropriate defaults: Provide sensible default values for required fields
- Include help text: Add helpful descriptions for complex fields
- Order thoughtfully: Use
displayOrder
to control field presentation
2. Data Types¶
- STRING vs TEXT: Use STRING for short values (< 255 chars), TEXT for longer content
- CHOICE vs MULTI_CHOICE: Use CHOICE for single selections, MULTI_CHOICE for multiple
- Date formats: Always use ISO format (YYYY-MM-DD) for dates
- JSON validation: Consider the complexity of JSON structures for user experience
3. Validation¶
- Be specific with constraints: Set appropriate min/max values and lengths
- Use regex sparingly: Only when necessary, and include clear help text
- Required vs optional: Carefully consider which fields should be required
4. Performance¶
- Batch operations: Use bulk mutations for updating multiple documents
- Selective queries: Only query the fields you need
- Cache schema: Metadata schemas change infrequently, cache them appropriately
5. Error Handling¶
- Check
ok
field: Always verify mutation success before proceeding - Display validation errors: Show clear error messages to users
- Graceful degradation: Handle missing or invalid data appropriately