Skip to main content
This tutorial uses POST /api/v3/content-types and GET /api/v3/content-types. The full schema lives in the API reference.
After this tutorial you’ll have a content type tree with custom attributes, the schema that powers all facet queries. You define this once at the company level, then apply it to as many files as you need. There are two ways to build your schema:
  1. Adopt starter templates: import ready-made trees for common verticals and customize them
  2. Build from scratch: create your own trees, nodes, and attributes entirely

Option A: Adopt starter templates

LightOn ships with starter templates for legal, finance, healthcare, tech, and manufacturing. Browse what’s available, adopt the ones you need, then customize.
browse_templates.py
This lists all available template roots. To adopt one or more:
adopt_templates.py
Once adopted, the trees become your company’s own content types, fully editable. You can rename nodes, add children, define new attributes, or delete what you don’t need using the same actions described below. Here’s what you get with legal and finance: Each node carries its own attributes and inherits from its parent. For example, legal defines confidentiality_level and jurisdiction. legal:contract adds parties, contract_value, and effective_date. legal:contract:nda adds counterparty, is_mutual, and duration_years, giving it 8 attributes total. Here’s a second schema with its attributes. A file can be classified under multiple trees at once: for example, an NDA could be both a legal:contract:nda and a finance:investment:due-diligence if it’s part of an investment deal.
Adoption behavior:
  • Trees are adopted wholesale: you cannot pick individual branches from a template.
  • Adoption is idempotent: re-adopting the same tree updates existing nodes based on their path. It does not auto-delete nodes you’ve removed or added since the last adoption.
  • After adoption, all content types are fully yours. The link to the original template is gone.

Option B: Build from scratch

Here’s what we’re going to build: All writes go to a single endpoint with an action field. Every action is idempotent and safe to replay. You can send a single action object or a JSON array to batch multiple actions in one call.

Step 1: Create your first Content Type

Start from zero and create a root content type called contract.
define_root_content_type.py
Returns 201 Created on first call, 200 OK if the node already exists (idempotent update). This makes scripts safe to run in CI pipelines or onboarding automations. path is the unique identifier you’ll use everywhere: to classify files, define attributes, and filter searches. For a root node, path equals code. inherit_attributes defaults to true, which means any attributes you define on contract are automatically available to all child types (contract:nda, contract:service-agreement, etc.). You define shared fields once on the parent.

Step 2: Build a tree of Content Types

Add child nodes. The path separator is :. LightOn builds the full path for you from parent_path + code.
define_child_content_types.py
Your tree is now:
You can go up to 4 levels deep. A company can have multiple independent trees (different roots). Each tree is completely isolated.

Read your classification tree at any time

Step 3: Define attributes

Attributes are the typed fields you want to store per document. Define them on a node, and if inherit_attributes: true, all children get them too. See Rules & constraints for naming restrictions, type immutability, and other validation rules.
Write descriptions for machines, not just humans. The description you give an attribute is what LightOn reads when it infers search filters from a natural-language query, so the wording changes which filter syntax it picks:
  • Person or entity names: include “name”, “author”, “examiner”, or “inventor” (e.g. "Full legal name of the other party in the agreement"). This signals a partial match is acceptable, so a query for “Acme” becomes counterparty:*Acme*.
  • Codes and identifiers: include “code”, “classification”, or “identifier” (e.g. "USPC classification code for the patent"). This signals exact match only, so the value is never guessed from surrounding topic words.

Shared attributes on contract (inherited by all subtypes)

define_shared_attributes.py
Because contract has inherit_attributes: true, every child type (contract:nda, contract:service-agreement, etc.) automatically exposes these four attributes. You don’t need to redefine them.

NDA-specific attributes (only on contract:nda)

define_nda_attributes.py

Service agreement-specific attributes

define_sa_attributes.py

How inheritance works

Attribute inheritance is resolved at query time: when you read a file’s facets, LightOn walks up the tree and collects attributes from each ancestor with inherit_attributes: true. Adding an attribute to a parent immediately makes it available on all descendants, no migration needed. For details on breaking the chain or shadowing inherited attributes, see Rules & constraints.

Full script: build the contract classification tree in one batch call

All schema operations can be batched in a single request by sending an {"actions": [...]} object to the /batch endpoint. LightOn processes them in order and fails fast on the first error. This is the recommended approach for onboarding automations.
batch_tree.py
The response is a {"results": [...]} object with one entry per action, in order. Each entry has status (201 created, 200 updated) and a data object. If any action fails, the batch stops at that point. Actions before the failure are committed; actions after are not.

Verify: read your complete classification tree

The response shows own attributes per node. Inherited attributes are resolved when you read a file’s facets, not pre-expanded here.

Action reference