Create an Ontology

An ontology defines the structured data you want to extract from a specific kind of document, together with the label hierarchy used to classify it. This guide walks you through designing, writing, registering, and testing an ontology from scratch.

Last updated: 2026-07-21

An ontology defines the structured data you want to extract from a specific kind of document, together with the label hierarchy used to classify it. This guide walks you through designing, writing, registering, and testing an ontology from scratch.

An ontology has two parts:

  1. Taxonomy — a hierarchy of labels used for document classification (e.g., invoiceutility_invoice).
  2. Fields — the structured data to extract, attached at each level of the taxonomy.

Extraction plugins use ontologies to know what data to extract and where to store it.


1. Design Your Schema

Start by identifying the document type you are targeting and listing every field you need to extract. Because the taxonomy is hierarchical, also decide which sub-types exist and which fields are specific to them.

Consider these questions:

  • What top-level document type does this ontology model? (This becomes the root taxonomy label.)
  • What sub-categories exist beneath it? (These become entries under children.)
  • What fields are always present? (Mark these required: true.)
  • What fields are sometimes present? (Leave these required: false.)
  • Which fields are specific to a sub-type versus inherited from the parent?

Our example:

invoice
  - invoice_total (required)
  - invoice_date (required)
  - vendor_name (required)
  - utility_invoice (sub-type)
      - utility_type (required)

2. Write the Ontology YAML

Ontologies are defined in YAML. The taxonomy is a tree: a root label with its own fields, and optional children, each of which can add fields of its own. Child labels inherit their parent's fields.

yaml
"color:#5c6370;font-style:italic"># Metadata
"color:#e06c75">name: "Invoice Ontology"
"color:#e06c75">description: "Schema for invoice document classification and data extraction"
"color:#e06c75">version: "1.0.0"

"color:#5c6370;font-style:italic"># Taxonomy — hierarchical labels
"color:#e06c75">taxonomy:
  "color:#e06c75">label: "invoice"
  "color:#e06c75">description: "General invoice document"

  "color:#5c6370;font-style:italic"># Fields for invoice documents
  "color:#e06c75">fields:
    - name: "invoice_total"
      "color:#e06c75">description: "Total amount on the invoice"
      "color:#e06c75">dtype: "number"
      "color:#e06c75">required: "color:#d19a66">true

    - name: "invoice_date"
      "color:#e06c75">description: "Date the invoice was issued (YYYY-MM-DD)"
      "color:#e06c75">dtype: "date"
      "color:#e06c75">required: "color:#d19a66">true

    - name: "invoice_number"
      "color:#e06c75">description: "Invoice number or ID"
      "color:#e06c75">dtype: "string"
      "color:#e06c75">required: "color:#d19a66">false

    - name: "vendor_name"
      "color:#e06c75">description: "Name of the vendor or service provider"
      "color:#e06c75">dtype: "string"
      "color:#e06c75">required: "color:#d19a66">true

    - name: "vendor_address"
      "color:#e06c75">description: "Vendor's address"
      "color:#e06c75">dtype: "string"
      "color:#e06c75">required: "color:#d19a66">false

    - name: "due_date"
      "color:#e06c75">description: "Payment due date (YYYY-MM-DD)"
      "color:#e06c75">dtype: "date"
      "color:#e06c75">required: "color:#d19a66">false

    - name: "line_items"
      "color:#e06c75">description: "Individual line items on the invoice"
      "color:#e06c75">dtype: "array"
      "color:#e06c75">required: "color:#d19a66">false

  "color:#5c6370;font-style:italic"># Child labels (sub-types of invoice)
  "color:#e06c75">children:
    - label: "utility_invoice"
      "color:#e06c75">description: "Invoice for utility services (electricity, water, gas)"

      "color:#5c6370;font-style:italic"># Additional fields specific to utility invoices
      "color:#e06c75">fields:
        - name: "utility_type"
          "color:#e06c75">description: "Type of utility (electricity, water, gas, internet)"
          "color:#e06c75">dtype: "string"
          "color:#e06c75">required: "color:#d19a66">true

        - name: "account_number"
          "color:#e06c75">description: "Utility account number"
          "color:#e06c75">dtype: "string"
          "color:#e06c75">required: "color:#d19a66">false

        - name: "usage_amount"
          "color:#e06c75">description: "Amount of utility consumed (kWh, gallons, etc.)"
          "color:#e06c75">dtype: "number"
          "color:#e06c75">required: "color:#d19a66">false

    - label: "service_invoice"
      "color:#e06c75">description: "Invoice for professional services"

      "color:#e06c75">fields:
        - name: "service_type"
          "color:#e06c75">description: "Type of service provided"
          "color:#e06c75">dtype: "string"
          "color:#e06c75">required: "color:#d19a66">true

        - name: "hourly_rate"
          "color:#e06c75">description: "Hourly rate for service"
          "color:#e06c75">dtype: "number"
          "color:#e06c75">required: "color:#d19a66">false

3. Validate with the SDK

Validate your ontology locally before registering it:

bash
pip install bizsupply-sdk
bizsupply init ontology --name my_ontology   ="color:#5c6370;font-style:italic"># Scaffold a template
bizsupply validate invoice_ontology.yaml     ="color:#5c6370;font-style:italic"># Validate the schema

Check these rules:

  1. Field types: each dtype must be one of string, number, date, boolean, array.
  2. Label names: use snake_case (e.g., utility_invoice, not Utility Invoice).
  3. Hierarchy: parent fields are inherited by children.
  4. Required fields: mark critical fields with required: true.

Flattened labels:

The platform flattens hierarchical labels so documents can be queried by parent or child:

  • invoice["invoice"]
  • utility_invoice["invoice", "utility_invoice"]

4. Register the Ontology

Register the ontology by uploading the YAML file as multipart form data:

bash
POST /api/v1/ontologies
Authorization: Bearer <your-jwt-token>
Content-Type: multipart/form-data

Fields:
- ontology_file: invoice_ontology.yaml

Using curl:

bash
curl -X POST "https://api.bizsupply.ai/api/v1/ontologies" \
  -H "Authorization: Bearer YOUR_JWT_TOKEN" \
  -F "ontology_file=@invoice_ontology.yaml"

Response:

json
{
  "ontology_id": "01HQZX3K4M2N5P7Q8R9S0T1U2V",
  "name": "Invoice Ontology",
  "message": "Ontology registered successfully"
}

Save the ontology_id — you'll need it when creating pipelines.


5. Verify Registration

bash
GET /api/v1/ontologies
Authorization: Bearer <your-jwt-token>
json
{
  "count": 1,
  "ontologies": [
    {
      "ontology_id": "01HQZX3K4M2N5P7Q8R9S0T1U2V",
      "name": "Invoice Ontology",
      "description": "Schema for invoice document classification and data extraction",
      "version": "1.0.0",
      "taxonomy": {
        "label": "invoice",
        "fields": [
          { "name": "invoice_total", "dtype": "number", "required": true }
        ],
        "children": [
          { "label": "utility_invoice", "fields": [] }
        ]
      }
    }
  ]
}

6. Create a Matching Extraction Plugin

Your Extraction plugin receives the ontology fields the platform resolves from the document's labels, and uses them to build the LLM prompt. Extraction is asynchronous, reads the raw file bytes (file_data), and returns an ExtractionResult.

python
from bizsupply_sdk import ExtractionPlugin, ExtractionResult, ConfigurableParameter


class InvoiceExtractorPlugin(ExtractionPlugin):
    """Extracts invoice data using ontology field definitions.

    The platform resolves fields from the ontology based on the document's
    labels and injects them as the `fields` parameter.
    """

    configurable_parameters = [
        ConfigurableParameter(
            parameter_name="extraction_prompt_id",
            parameter_type="str",
            default_value=None,
            description="Prompt ID for extraction",
        ),
    ]

    async def extract(self, document, file_data, mime_type, fields, configs) -> ExtractionResult:
        """Extract structured data from a single document.

        Args:
            document: The classified document.
            file_data: Raw file bytes (pre-fetched by the platform).
            mime_type: MIME type of the file.
            fields: Ontology fields resolved from document.labels.
            configs: Runtime configuration.

        Returns:
            An ExtractionResult with the extracted data.
        """
        fields_json = self.format_fields_for_prompt(fields)

        prompt = f"""Extract the following invoice data from this document.

Fields to extract:
{fields_json}

Return a JSON object with the field names as keys and the extracted values."""

        # The LLM reads the attached file bytes directly.
        result = await self.prompt_llm(
            prompt=prompt,
            file_data=file_data,
            mime_type=mime_type,
        )

        if not result or not isinstance(result, dict):
            return ExtractionResult(data={})

        return ExtractionResult(
            data=result,
            llm_fields=list(result.keys()),
        )

prompt_llm(...) returns the parsed response (dict | list | None); it accepts file_data, mime_type, an optional schema, and an optional model_name. The platform handles persistence once you return the ExtractionResult.


7. Wire It Into a Pipeline and Execute

Reference the ontology ID (and your source, classifier, and extractor plugins) when creating a pipeline:

bash
POST /api/v1/pipelines
Authorization: Bearer <your-jwt-token>
Content-Type: application/json

{
  "name": "Invoice Processing Pipeline",
  "description": "Classify and extract invoice data",
  "plugin_ids": [
    "YOUR_SOURCE_PLUGIN_ID",
    "YOUR_CLASSIFIER_PLUGIN_ID",
    "YOUR_EXTRACTOR_PLUGIN_ID"
  ],
  "ontology_catalogs_ids": [
    "01HQZX3K4M2N5P7Q8R9S0T1U2V"
  ]
}

Then start a job from the pipeline and inspect the extracted fields:

bash
="color:#5c6370;font-style:italic"># Execute
POST /api/v1/jobs/from-pipeline
Authorization: Bearer <your-jwt-token>
Content-Type: application/json

{ "pipeline_id": "01HQZY5M7N8P9Q0R1S2T3U4V5W" }
bash
="color:#5c6370;font-style:italic"># Check results
GET /api/v1/documents?labels=invoice
Authorization: Bearer <your-jwt-token>
json
{
  "total_count": 5,
  "documents": [
    {
      "id": "01HR001A2B3C4D5E6F7G8H9J0K",
      "labels": ["invoice"],
      "data": {
        "invoice_total": 1500.00,
        "invoice_date": "2025-10-15",
        "invoice_number": "INV-12345",
        "vendor_name": "Acme Corp",
        "due_date": "2025-11-15"
      }
    }
  ],
  "next_cursor": null
}

See Create a Pipeline for the full pipeline workflow.


8. Iterate on Field Descriptions

The quality of extracted data depends heavily on field descriptions — they are included directly in the LLM prompt. If the LLM extracts incorrect values, make the description more specific about where to find the value and how to format it. A clear description like "The purchase order number, typically formatted as PO-YYYY-NNNNN" produces better results than a vague one like "PO number".


9. Update the Ontology

To add or modify fields, re-upload the YAML file:

bash
PUT /api/v1/ontologies/{ontology_id}
Authorization: Bearer <your-jwt-token>
Content-Type: multipart/form-data

Fields:
- ontology_file: updated_invoice_ontology.yaml

The version auto-increments on update, and pipelines that reference the ontology pick up the new version automatically.


Field Type Reference

Each field's dtype is one of:

dtypeDescriptionExample
stringText value"Acme Corp"
numberNumeric value (integer or decimal)1500.00
dateDate value, normalized to YYYY-MM-DD"2025-10-15"
booleanTrue/false valuetrue
arrayList of values or objects["item1", "item2"]

LLM-Powered Ontology Generation (Ontology Fabric)

Instead of writing YAML manually, you can describe your ontology in natural language and let the platform generate it using an LLM.

How It Works

User prompt (natural language)
    |
    v
POST /api/v1/ontologies/preview
    |-- LLM generates the taxonomy
    |-- SDK validates the structure
    |-- Returns an editable taxonomy (no persistence)
    v
User edits the taxonomy (optional)
    |
    v
POST /api/v1/ontologies/generate
    |-- SDK validates the edited taxonomy
    |-- Scoring (structural + overlap + LLM quality)
    |-- Score >= 7.0 -> Auto-approved, persisted immediately
    |-- Score < 7.0  -> Sent for human review
    v
A platform administrator reviews (if needed)
    |-- GET  /api/v1/ontologies/reviews/pending
    |-- POST .../approve -> Ontology persisted
    |-- POST .../reject  -> Ontology discarded with a reason

Step 1: Preview (No Commitment)

Describe what you need in plain English:

bash
curl -X POST "https://api.bizsupply.ai/api/v1/ontologies/preview" \
  -H "Authorization: Bearer YOUR_JWT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"prompt": "Create an ontology for classifying energy supply contracts with fields for pricing, consumption periods, and renewal dates"}'

The response includes the generated taxonomy as an editable tree, plus the label hierarchy chain and the field list. You can modify the taxonomy before submitting it.

Step 2: Generate (Validate + Score + Persist)

Submit the (optionally edited) taxonomy for validation and scoring:

bash
curl -X POST "https://api.bizsupply.ai/api/v1/ontologies/generate" \
  -H "Authorization: Bearer YOUR_JWT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "taxonomy": {"label": "contract", "children": [...]},
    "scope": "tenant",
    "name": "Energy Contract Ontology",
    "description": "Schema for energy supply contracts"
  }'

The response review_status field indicates the outcome:

  • APPROVED — the score was 7.0 or higher. The ontology is persisted and ready to use.
  • HUMAN_REVIEW — the score was below 7.0. A platform administrator must approve it before it becomes available.

Credit Gating

Both preview and generate check the tenant's credit balance before calling the LLM. If the tenant has no remaining credits, the API returns 402 Payment Required with an InsufficientCreditsError.

Scoring Dimensions

The quality score (0–10) evaluates four dimensions:

DimensionWhat It Checks
CompletenessRequired fields present, descriptions populated, hierarchy depth
Field qualityValid types, meaningful names, required/optional balance
Naming conventionssnake_case labels, consistent naming patterns
Hierarchy depthAppropriate nesting (not too flat, not too deep)

Overlap Detection

Before persisting, the platform checks for overlap with existing ontologies in the same tenant:

  • It computes Jaccard similarity on the label sets of the new and existing ontologies.
  • If overlap exceeds 50%, the quality score is capped at 5.0, forcing human review.
  • The review observations field explains the overlap finding.

Human Review

Ontologies scoring below 7.0 enter the review queue. Platform administrators can:

  • List pending reviews: GET /api/v1/ontologies/reviews/pending
  • Approve: POST /api/v1/ontologies/reviews/{review_id}/approve — persists the ontology
  • Reject: POST /api/v1/ontologies/reviews/{review_id}/reject — discards it with a reason

Common Issues

IssueCauseResolution
Invalid field type: 'decimal'Unsupported dtype.Use number. Supported: string, number, date, boolean, array.
Child labels not inheriting parent fieldsChildren aren't nested under children:.Ensure each sub-type is listed under the parent's children: block.
Fields not extractedExtraction didn't resolve the ontology fields, or classification didn't run.Confirm the document has the expected labels and that the ontology ID is in the pipeline's ontology_catalogs_ids.
Required field returns nullThe field is absent, or the LLM missed it.Improve the field description; consider making it optional if it is legitimately absent on some documents.
Ontology not found in pipelineWrong or foreign ontology_id.Verify with GET /api/v1/ontologies/{ontology_id} that it exists and belongs to your tenant.

Best Practices

  1. Write precise field descriptions — the description is the single biggest factor in extraction quality. Be specific about location, format, and common variations.
  2. Start with required fields only — add optional fields after you confirm the required ones extract correctly.
  3. Use hierarchies — organize labels from general to specific (invoiceutility_invoice).
  4. Consistent naming — use snake_case for all labels and field names.
  5. Version carefully — update the ontology by re-uploading; the version auto-increments and pipelines pick up the change. Test on a small batch before relying on it in production.
  6. Test with real documents — synthetic samples rarely capture real-world variability. Validate against at least 10 real documents.

Next Steps