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

# Import & Export

> Import and export data contracts using the Open Data Contract Standard (ODCS) v3.1.0 format

# Import & Export

Data contracts can be imported and exported using the [Open Data Contract Standard (ODCS)](https://bitol-io.github.io/open-data-contract-standard/latest/) v3.1.0 format. Both JSON and YAML are supported, with smart merge and full replace modes for updates.

For which ODCS fields an import keeps, how quality rules become test cases, and how exports differ from the Bitol JSON Schema, see [ODCS Import and Export](/v2.1.x-SNAPSHOT/how-to-guides/data-contracts/odcs).

## Export to ODCS

### Export by ID (JSON)

`GET /v1/dataContracts/{id}/odcs`

### Export by ID (YAML)

`GET /v1/dataContracts/{id}/odcs/yaml`

### Export by FQN (JSON)

`GET /v1/dataContracts/name/{fqn}/odcs`

### Export by FQN (YAML)

`GET /v1/dataContracts/name/{fqn}/odcs/yaml`

<ParamField path="id" type="string">
  UUID of the data contract.
</ParamField>

<ParamField path="fqn" type="string">
  Fully qualified name of the data contract.
</ParamField>

<ParamField query="fields" type="string">
  Fields to include in the export. Defaults to `owners,reviewers,extension,schema,sla,security`.
</ParamField>

<RequestExample dropdown>
  ```python Export ODCS theme={null}
  from metadata.sdk import configure
  from metadata.sdk.entities import DataContracts

  configure(
      host="https://your-company.open-metadata.org/api",
      jwt_token="your-jwt-token"
  )

  # Export as ODCS JSON object
  odcs = DataContracts.export_odcs(contract_id).execute()
  print(f"ODCS version: {odcs.apiVersion}")
  print(f"Status: {odcs.status}")

  # Export as YAML string
  yaml_str = DataContracts.export_odcs(contract_id).as_yaml().execute()
  print(yaml_str)

  # Export by FQN
  odcs = DataContracts.export_odcs_by_name("sales-orders-contract").execute()
  ```

  ```java Export ODCS theme={null}
  // Export using the REST client
  var odcs = client.dataContracts().exportToODCS(contractId);
  System.out.println("ODCS version: " + odcs.getApiVersion());

  // Export as YAML
  String yaml = client.dataContracts().exportToODCSYaml(contractId);
  ```

  ```bash Export ODCS theme={null}
  # Export as JSON
  curl "{base_url}/api/v1/dataContracts/f7a1b2c3-d4e5-6789-0abc-def123456789/odcs" \
    -H "Authorization: Bearer {access_token}"

  # Export as YAML
  curl "{base_url}/api/v1/dataContracts/f7a1b2c3-d4e5-6789-0abc-def123456789/odcs/yaml" \
    -H "Authorization: Bearer {access_token}" \
    -H "Accept: application/yaml"

  # Export by FQN
  curl "{base_url}/api/v1/dataContracts/name/sales-orders-contract/odcs" \
    -H "Authorization: Bearer {access_token}"
  ```
</RequestExample>

<ResponseExample>
  ```json Response (ODCS JSON) theme={null}
  {
    "apiVersion": "v3.1.0",
    "kind": "DataContract",
    "name": "sales-orders-contract",
    "version": "1.0.0",
    "status": "active",
    "domain": "ecommerce",
    "description": {
      "purpose": "Data contract for the sales orders table"
    },
    "schema": [
      {
        "name": "orders",
        "logicalType": "object",
        "physicalType": "table",
        "properties": [
          {
            "name": "order_id",
            "physicalName": "order_id",
            "logicalType": "integer",
            "physicalType": "INT",
            "primaryKey": true,
            "unique": false,
            "required": false,
            "quality": [
              {
                "id": "order_id_not_null",
                "name": "Order ID is never null",
                "type": "library",
                "metric": "nullValues",
                "mustBe": 0,
                "column": "order_id"
              }
            ]
          },
          {
            "name": "customer_id",
            "physicalName": "customer_id",
            "logicalType": "integer",
            "physicalType": "INT",
            "primaryKey": false,
            "unique": false,
            "required": true
          },
          {
            "name": "order_date",
            "physicalName": "order_date",
            "logicalType": "timestamp",
            "physicalType": "TIMESTAMP"
          }
        ],
        "quality": [
          {
            "id": "orders_not_empty",
            "name": "Orders table is not empty",
            "type": "library",
            "metric": "rowCount",
            "mustBeGreaterThan": 0,
            "column": "orders"
          }
        ]
      }
    ],
    "slaProperties": [
      {
        "property": "freshness",
        "value": "1",
        "unit": "day",
        "element": "order_date"
      },
      {
        "property": "availabilityTime",
        "value": "09:00",
        "valueExt": "GMT+00:00 (Europe/London)"
      }
    ]
  }
  ```
</ResponseExample>

***

## Import from ODCS

### Create from ODCS JSON

`POST /v1/dataContracts/odcs`

### Create from ODCS YAML

`POST /v1/dataContracts/odcs/yaml`

### Create or Update from ODCS JSON

`PUT /v1/dataContracts/odcs`

### Create or Update from ODCS YAML

`PUT /v1/dataContracts/odcs/yaml`

<ParamField query="entityId" type="string" required>
  UUID of the entity to attach the contract to.
</ParamField>

<ParamField query="entityType" type="string" required>
  Type of the entity (e.g., `table`, `topic`, `apiEndpoint`).
</ParamField>

<ParamField query="mode" type="string" default="merge">
  Import mode for PUT endpoints: `merge` (preserves existing fields not in the import) or `replace` (fully overwrites the contract).
</ParamField>

<ParamField query="objectName" type="string">
  Schema object name to import for multi-object ODCS contracts. If not specified, auto-selects based on entity name.
</ParamField>

<ParamField query="createTestCases" type="boolean" default="true">
  Whether the contract's ODCS quality rules become test cases on the table. With `false`, the rules are stored with the contract without running, and test cases already linked to the contract stay linked, in `replace` mode too. Creating test cases requires the **Create Tests** permission on the table. Without it, an import with `createTestCases=true` returns `403` and writes nothing.
</ParamField>

```python Import ODCS theme={null}
from metadata.sdk import configure
from metadata.sdk.entities import DataContracts

configure(
    host="https://your-company.open-metadata.org/api",
    jwt_token="your-jwt-token"
)

# Import from ODCS object (creates new contract)
contract = (DataContracts.import_odcs(table_id, "table")
    .from_odcs(odcs_contract)
    .execute())

# Import from YAML with smart merge
yaml_content = open("contract.yaml").read()
contract = (DataContracts.import_odcs(table_id, "table")
    .from_yaml(yaml_content)
    .merge()
    .execute())
```

```java Import ODCS theme={null}
// Import from ODCS JSON
var contract = client.dataContracts().importFromODCS(
    odcsContract, entityId, "table"
);

// Import from YAML
var contract = client.dataContracts().importFromODCSYaml(
    yamlContent, entityId, "table"
);

// Import from YAML, keeping quality rules without creating test cases
var contract = client.dataContracts().importFromODCSYaml(
    yamlContent, entityId, "table", false
);
```

```bash Import ODCS theme={null}
# Import from ODCS JSON (create new)
curl -X POST "{base_url}/api/v1/dataContracts/odcs?entityId=a1b2c3d4-e5f6-7890-abcd-ef1234567890&entityType=table" \
  -H "Authorization: Bearer {access_token}" \
  -H "Content-Type: application/json" \
  -d @contract.json

# Import from ODCS YAML
curl -X POST "{base_url}/api/v1/dataContracts/odcs/yaml?entityId=a1b2c3d4-e5f6-7890-abcd-ef1234567890&entityType=table" \
  -H "Authorization: Bearer {access_token}" \
  -H "Content-Type: application/yaml" \
  --data-binary @contract.yaml

# Create or update with smart merge
curl -X PUT "{base_url}/api/v1/dataContracts/odcs?entityId=a1b2c3d4-e5f6-7890-abcd-ef1234567890&entityType=table&mode=merge" \
  -H "Authorization: Bearer {access_token}" \
  -H "Content-Type: application/json" \
  -d @contract.json

# Full replace
curl -X PUT "{base_url}/api/v1/dataContracts/odcs?entityId=a1b2c3d4-e5f6-7890-abcd-ef1234567890&entityType=table&mode=replace" \
  -H "Authorization: Bearer {access_token}" \
  -H "Content-Type: application/json" \
  -d @contract.json

# Import quality rules without creating test cases
curl -X POST "{base_url}/api/v1/dataContracts/odcs/yaml?entityId=a1b2c3d4-e5f6-7890-abcd-ef1234567890&entityType=table&createTestCases=false" \
  -H "Authorization: Bearer {access_token}" \
  -H "Content-Type: application/yaml" \
  --data-binary @contract.yaml
```

An import reads past values it can't represent. It leaves them out and imports the rest. Only the blocking issues listed under [Validate ODCS YAML](#validate-odcs-yaml) reject the request.

***

## Preserving ODCS-Only Fields

ODCS documents can carry data OpenMetadata doesn't model directly, such as element-level
`authoritativeDefinitions` and `transformSourceObjects`. Rather than dropping this data, OpenMetadata stores it
as a passthrough and restores it on export. ODCS quality rules are stored the same way, whether or not they
also run as test cases, so an export returns them word for word.

<Warning>
  A plain `PUT /v1/dataContracts` that omits these fields from its payload leaves them untouched—only an
  explicit empty array in the payload clears them, the same way `PUT /odcs?mode=replace` does with a document
  that omits them. A PATCH that explicitly removes them also works.
</Warning>

**Current limitations**:

* The contract page doesn't show these passthrough fields. Quality rules that run show up as the contract's test cases. The import report lists every rule, including the ones that don't run.
* If the schema element an extension is attached to can't be matched on export (for example, it was renamed
  or removed), `authoritativeDefinitions` fall back to the contract level rather than being lost.
  `transformSourceObjects` have no such fallback. For an unmatched element, they're dropped instead.
* Several other ODCS attributes aren't mapped yet and don't round-trip: `businessName`, `classification`,
  `examples`, `customProperties`, `transformLogic`, `transformDescription`, `logicalTypeOptions` (except
  `maxLength`, which round-trips through the column's `dataLength`), and element-level `tags`. For the full list,
  see [Field Mapping](/v2.1.x-SNAPSHOT/how-to-guides/data-contracts/odcs#field-mapping).

***

## Parse ODCS YAML

Preview an ODCS YAML contract without importing. Useful for inspecting multi-object contracts.

`POST /v1/dataContracts/odcs/parse/yaml`

```bash Parse YAML theme={null}
curl -X POST "{base_url}/api/v1/dataContracts/odcs/parse/yaml" \
  -H "Authorization: Bearer {access_token}" \
  -H "Content-Type: application/yaml" \
  --data-binary @contract.yaml
```

```json Response theme={null}
{
  "name": "sales-contract",
  "version": "1.0.0",
  "status": "active",
  "schemaObjects": ["orders", "order_items"],
  "hasMultipleObjects": true
}
```

***

## Validate ODCS YAML

Preview an ODCS YAML import against a target entity without creating anything. The response reports what the import keeps, changes, and leaves out, and what each quality rule becomes. The UI's import dialog shows the same report.

`POST /v1/dataContracts/odcs/validate/yaml`

<ParamField query="entityId" type="string" required>
  UUID of the entity to validate against.
</ParamField>

<ParamField query="entityType" type="string" required>
  Type of the entity.
</ParamField>

<ParamField query="objectName" type="string">
  Schema object name for multi-object contracts.
</ParamField>

<ParamField query="createTestCases" type="boolean" default="true">
  Whether the import you're previewing would create test cases from the quality rules. Pass the same value you'll import with, so the report matches the import.
</ParamField>

```bash Validate YAML theme={null}
curl -X POST "{base_url}/api/v1/dataContracts/odcs/validate/yaml?entityId=a1b2c3d4-e5f6-7890-abcd-ef1234567890&entityType=table" \
  -H "Authorization: Bearer {access_token}" \
  -H "Content-Type: application/yaml" \
  --data-binary @contract.yaml
```

```java Validate YAML theme={null}
ContractValidation validation = client.dataContracts().validateODCSYaml(
    yamlContent, entityId, "table", true
);
ODCSImportReport report = validation.getOdcsImportReport();
System.out.println("Can import: " + report.getCanImport());
```

```json Response theme={null}
{
  "valid": true,
  "schemaValidation": {
    "passed": 3,
    "failed": 0,
    "total": 3,
    "failedFields": []
  },
  "odcsImportReport": {
    "odcsVersion": "v3.1.0",
    "canImport": true,
    "canCreateTestCases": true,
    "issues": [
      {
        "severity": "warning",
        "category": "schema",
        "field": "businessName",
        "path": "schema[0].properties[0].businessName",
        "occurrences": 3,
        "message": "`businessName` is not imported. OpenMetadata contract columns have no equivalent."
      },
      {
        "severity": "warning",
        "category": "servers",
        "field": "servers",
        "path": "servers",
        "occurrences": 1,
        "message": "`servers` is not imported. OpenMetadata takes connection details from the table's service."
      },
      {
        "severity": "info",
        "category": "schema",
        "field": "authoritativeDefinitions",
        "path": "schema[0].authoritativeDefinitions",
        "occurrences": 1,
        "message": "`authoritativeDefinitions` is kept for ODCS export but not shown in OpenMetadata."
      }
    ],
    "qualityRules": [
      {
        "name": "Order ID is never null",
        "column": "order_id",
        "outcome": "testCase",
        "testDefinition": "columnValuesToBeNotNull",
        "testCaseName": "order_id_not_null"
      },
      {
        "name": "Orders land daily",
        "column": "orders",
        "outcome": "sla",
        "reason": "Sets the contract's refresh frequency to every day."
      },
      {
        "name": "Soda duplicate check",
        "column": "orders",
        "outcome": "notExecuted",
        "reason": "It is written for the 'soda' engine, which runs outside OpenMetadata."
      }
    ]
  }
}
```

The response is a `ContractValidation`. `valid` matches `odcsImportReport.canImport`.

### Import Report Fields

| Field | Description |
| - | - |
| `odcsVersion` | The `apiVersion` the document declares. |
| `canImport` | `false` when at least one issue is blocking. |
| `canCreateTestCases` | Whether you may create test cases on the table, which importing quality rules as test cases requires. |
| `issues` | Findings about the document, blocking issues first, then warnings, then information. |
| `qualityRules` | One entry per quality rule, with what the rule becomes. |

Each issue has these fields:

| Field | Description |
| - | - |
| `severity` | `blocking` stops the import. `warning` means part of the document isn't imported, or not as written. `info` describes how something is imported, such as a field kept for export only. |
| `category` | Section of the document: `document`, `schema`, `quality`, `sla`, `team`, `roles`, `servers`, `support`, or `other`. |
| `field` | The ODCS key the finding is about, such as `businessName`. |
| `path` | Where the first occurrence is, such as `schema[0].properties[3].businessName`. |
| `occurrences` | How many places the same finding applies to. A key repeated on every property is reported once with a count. |
| `message` | What happens to the field and why. |

Each quality rule entry has these fields:

| Field | Description |
| - | - |
| `name`, `column` | The rule's name and the column or schema object it's attached to. |
| `outcome` | `testCase`: the rule runs as a test case. `sla`: the rule sets the contract's refresh frequency. `notExecuted`: the rule is stored with the contract, but nothing runs it. |
| `testDefinition`, `testCaseName` | For `testCase`, the test definition and the name of the test case the import creates or updates. |
| `reason` | For `notExecuted`, why the rule doesn't run. For `sla`, the refresh frequency it sets. |

These issues are blocking:

* An unsupported `apiVersion`, a `kind` other than `DataContract`, or a missing or invalid `status`.
* A contract column the table doesn't have, or a column listed twice.
* An `objectName` that isn't in the document, or a missing entity.
* Quality rules that would create test cases the caller may not create. Validate and import with `createTestCases=false` to keep the rules without running them.

A document with missing required fields returns `200` with `valid: false` and a blocking issue. Malformed YAML returns `400`.

***

## Supported ODCS Versions

| Version | Status |
| - | - |
| v3.2.0 | Supported. Read with the v3.1.0 mapping. Fields added in v3.2.0 are reported as not imported. |
| v3.1.0 | Full support. Exports use this version. |
| v3.0.2 | Supported |
| v3.0.1 | Supported |
| v3.0.0 | Supported |
| v2.2.2 | Supported |
| v2.2.1 | Supported |
| v2.2.0 | Supported |

***

## Error Handling

| Code | Error Type | Description |
| - | - | - |
| `400` | `BAD_REQUEST` | Invalid ODCS content or YAML syntax |
| `401` | `UNAUTHORIZED` | Invalid or missing authentication token |
| `403` | `FORBIDDEN` | User lacks permission for the contract, or for the test cases an import with `createTestCases=true` would create |
| `404` | `NOT_FOUND` | Data contract or entity not found |


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