Modular Ontology Design: API Checklist
Modular Ontology Design: API Checklist
If I had to cut this article down to one rule, it would be this: split commodity API ontology work into small modules, lock down IDs and mappings before release, and test every rule against live payloads.
I’d use this checklist to review five things before shipping: scope, classes and properties, URIs, source and field mapping, and validation. The main point is simple: keep price, unit, currency, and time-series logic separate, then make sure they still join correctly. That helps limit change spillover, keeps JSON-to-term mapping clear, and reduces schema drift.
Here’s the short version:
- Start with scope first. Define the questions each module must answer.
- Split by job. Keep commodity identity, observations, futures, units/currency, and history in separate parts.
- Map fields one time. Terms like
price,currency,unit,code, and timestamps should have one clear target. - Keep IDs stable. Versioned URIs and fixed naming rules prevent broken links.
- Validate before release. Run reasoner checks, SHACL checks, contract tests, and cross-module query tests.
A few details stand out:
- A price observation should have 1 price, 1 currency, and 1 unit.
- Timestamps should use ISO 8601, like
2026-07-12T01:10:43Z. - Futures IDs should stay separate from spot IDs.
- Optional fields like
changecan benull, so the model should allow that. - Freshness checks should compare “now” against fields like
updated_at.
In plain terms, I see this article as a pre-release review list for teams that publish commodity data APIs and need clean ontology modules for both live and historical data.
Modular Ontology Design: 5-Step Pre-Release Checklist for Commodity APIs
1. Scope and Module Boundaries Checklist
Before you write a single class or property, decide what each module is supposed to do. If you skip this step, scope drift can creep in fast. And once that happens, ontology work tends to slow down or stop. Module responsibility comes first. Field mapping comes after that.
Define competency questions and API use cases
Start with the questions the module needs to answer. If a concept doesn't help answer one of those questions, cut it. That's the simplest way to keep scope tight.
For a commodity API, the main questions usually look like this:
| Domain | Key Competency Question | Relevant API Fields |
|---|---|---|
| Spot Prices | What is the latest price and 24-hour change? | price, currency, unit, change |
| Futures | Which contract is currently the front-month? | contract_month, is_front_month, contract_status |
| Drilling | What is the rig count trend in a specific basin? | total_rigs, basin, week_change |
| Storage | What are the current US inventory levels? | level, unit, updated_at |
| Alerts | Has the price crossed a specific threshold? | condition_operator, condition_value, triggered_at |
A good rule here is blunt but useful: if a field doesn't help answer one of these questions, it doesn't belong in the module.
Separate core modules by domain responsibility
Once the competency questions are clear, split the model into focused modules. For commodity APIs, a clean setup often looks like this:
- A Commodity module handles static metadata, such as codes like
BRENT_CRUDE_USDand category labels. - A Pricing/Observation module owns the numeric price, change percentage, and observation timestamp.
- An Instrument/Futures module manages contract-level data like
contract_monthanddelivery_month. - A Units and Currency module standardizes measures like barrels, gallons, MMBtu, metric tons, and currencies like USD.
- A Time-Series module handles historical row semantics, including
start_date,end_date, and aggregation intervals.
This split matters. Contract metadata belongs in the Futures module. Substance identity belongs in the Commodity module. They can connect, of course, but that link should be explicit and documented. Don't leave it implied.
Document non-goals and out-of-scope data
Not everything in an API response belongs in your ontology. In fact, a lot of it doesn't.
Leave out transport, display, and wrapper data. HTTP status codes, rate-limit headers, and response envelopes belong to the API layer, not the ontology. Pre-formatted display strings like "formatted": "$70.59" are a client-side job. Regional cash gas prices should not sit next to Henry Hub or WTI futures in the same market model, because that can lead to bad comparisons. Delayed or reference curves such as JKM LNG should be treated as reference data, not core market data.
| Data Category | Out of Scope | Reason |
|---|---|---|
| Transport | HTTP headers, rate limit status | API management layer, not domain ontology |
| UI/Display | Formatted price strings (e.g., "$70.59") |
Client-side formatting responsibility |
| Market Context | Regional cash gas (for Henry Hub) | Prevents inaccurate benchmark comparisons |
| Raw Wrappers | JSON response envelope fields | Creates bloated, brittle models |
Write these exclusions into the module spec. Then, when a new field shows up during development, use the non-goals list as a filter. It helps stop the model from turning into a junk drawer.
With scope fixed, move to class and property review.
sbb-itb-a92d0a3
2. Class, Property, and Naming Checklist
With module boundaries set, the next step is making sure your classes and properties match what the API sends back. The module map should give each term one job.
Review core classes and observation patterns
The biggest split in any commodity ontology is identity vs. measurement.
A Commodity class describes what the asset is. It uses a code like [BRENT_CRUDE_USD](https://blog.oilpriceapi.com/p/brent-crude-origin).
A PriceObservation class describes what was recorded at a given moment. That includes the numeric value and the metadata tied to that point in time.
This split matters. It keeps JSON-to-ontology mapping steady across endpoints. Use the scope map above so each term lives in only one module.
You’ll also want support classes for source, unit, currency, and futures data. Those classes back feeds for latest price, price history, futures curves, and summary data.
Define properties, domains, ranges, and constraints
Once the classes are clear, define each property with a specific domain, range, and cardinality rule. This is where things often go sideways. If property definitions are loose, the ontology can drift without anyone noticing.
| JSON Field | Ontology Property | Data Type / Range | Cardinality |
|---|---|---|---|
price |
priceAmount |
Decimal / Float | 1 per observation |
currency |
quotedInCurrency |
ISO 4217 currency code | 1 per price record |
created_at |
observationTimestamp |
ISO 8601 String | 1 per observation |
contract_month |
deliveryMonth |
String (YYYY-MM) | 1 per futures contract |
source |
dataSource |
stable source label | 1 per record |
Cardinality rules help stop duplicate or unclear records. For source, use a stable label like market_reporting for provider data. That way, integrations don’t break if the upstream provider changes.
Once the property map is fixed, check naming and URI stability.
Apply naming rules that map cleanly to JSON fields
Naming drift creates quiet costs. When an ontology term doesn’t line up with its JSON field, someone has to build a translation layer. That means more upkeep and more chances for mapping bugs.
The rule is simple:
- Use
snake_casefor JSON keys, such asprice_amountandcreated_at - Use
UPPER_SNAKE_CASEfor commodity codes, such asWTI_USD - Use ISO 8601 for all timestamps, such as
2026-07-12T13:10:43Z
Commodity codes should include both the asset and the currency denomination. Use WTI_USD, not just WTI. That avoids confusion in multi-currency settings.
Treat codes as case-insensitive on input, but always return them in uppercase so parsing stays predictable.
Match API and SDK parameter names exactly to avoid mapping errors.
After names line up, move to URI versioning and source mapping.
3. URI, Source Mapping, Units, and Time-Series Checklist
With naming rules in place, the next step is the plumbing underneath: how terms get IDs, where each data point came from, and how prices, units, and timestamps are stored. These rules keep identifiers, provenance, units, and time-series data steady in production. And those identifiers need to stay in line with the naming rules above.
Set a stable URI and versioning plan
A clear URI plan keeps integrations steady as the ontology changes over time. Put the version in the path so updates can stay backward compatible.
Keep the namespace predictable and make the version visible. For example, a base path like https://api.oilpriceapi.com/v1/ shows the version in every request. Commodity records should use code, while futures contracts should use contract-month URIs.
Keep commodity records and futures contracts on separate paths. Futures and other special instruments should use product-scoped slugs so they don't get mixed up with spot data. For example, use /v1/futures/ice-brent/curve for a forward curve or /v1/futures/continuous/brent for a rolling series that abstracts individual contract rolls.
For single futures contracts, use a [CODE]_[YYYY]_[MM] format. A case like BRENT_FUTURES_2026_07 gives each delivery month its own clear ID.
Map source fields to ontology terms
Map ONLY the fields that affect identity, provenance, units, or time-series meaning. Here’s the direct field-to-property reference for commodity API responses:
| JSON Field | Ontology Property Type | Example Value |
|---|---|---|
price |
Decimal/Float | 85.42 |
currency |
ISO Currency Code | USD |
unit |
Measurement Unit | barrel, MMBtu, oz, gal |
code |
Unique Identifier | BRENT_CRUDE_USD |
created_at |
ISO 8601 Timestamp | 2026-07-02T13:10:43Z |
source |
Provenance/Organization | ICE, CME |
change |
Percentage/Decimal (Optional) | -0.45 or null |
Two fields need extra care.
sourceshould map to a stable provenance property.changecan come back asnullin a valid success response, so treat it as optional in the ontology.
Use code for commodity identity. Use contract slugs for futures URIs.
Handle USD pricing, commodity units, and timestamped series
Normalize every price to decimal notation before release. For example, store $85.42, not 8542.
Assign one quoted unit to each commodity code:
| Commodity Code | Standard Unit |
|---|---|
BRENT_CRUDE_USD |
USD/barrel (bbl) |
WTI_USD |
USD/barrel (bbl) |
NATURAL_GAS_USD |
USD/MMBtu |
GOLD_USD |
USD/ounce (oz) |
DIESEL_USD |
USD/gallon (gal) |
Use created_at for ingestion time and source_timestamp for market time. For historical ranges, use daily, weekly, or monthly intervals.
Before comparing futures against spot benchmarks, check contract_month and is_front_month. Those mappings feed straight into the validation checks in the next section.
4. Validation and Release Readiness Checklist
With URI plans, source mappings, and time-series handling locked in, the last step is simple: make sure the whole thing works before you ship it. This is where automated checks earn their keep. They help you confirm that mapped classes, units, and timestamps still behave the way you expect in live payloads.
Run logical validation and data-shape checks
Start with a reasoner consistency check. If any classes are unsatisfiable, fix them before release.
Then run SHACL validation against sample commodity payloads. Every price observation must include exactly one price, one currency, and one unit. Also check that commodity codes are normalized and case-insensitive.
| Validation Category | Check | Expected Outcome |
|---|---|---|
| Logical | Reasoner consistency | Zero unsatisfiable classes |
| Shape (SHACL) | Domain, range, and cardinality | price, unit, and currency always present |
| URI Integrity | Version check | /v1/ resolves and links do not break |
| Interoperability | Cross-module query testing | Successful joins between Commodity and Unit modules |
Once internal consistency is clean, test the modules together. That's the point where small mismatches tend to show up.
Test cross-module interoperability and API fit
Run cross-module queries across Currency, Unit, Commodity, and Futures modules. One solid release gate is pulling a front-month futures price along with exchange metadata. If the modules don't join cleanly on that query, they're not ready.
Use contract tests to confirm the ontology output matches the expected API response shape. status, data, and metadata all need to be present. Check that data_age_warning flags fire when the gap between now and updated_at goes past your defined freshness threshold.
Error handling needs a check too. Validate that error responses return a structured body with message, an HTTP error code such as 401 Unauthorized, and details.
Conclusion: Final Pre-Release Checklist Summary
If all checks pass, the module is release-ready. Ship only after reasoner, SHACL, URI, contract, freshness, and error-response checks pass.
FAQs
Why split price, unit, currency, and time-series logic into separate modules?
Splitting these into separate modules makes a commodity API easier to maintain and less likely to break. It also helps keep the data accurate, because you can handle source-specific mappings on their own, whether you're working with spot benchmarks or different futures contract months.
That separation matters when you need to compare instruments like continuous series and front-month contracts. Without it, it's easy to blend stale data with data that doesn't match. Separate modules also make validation tighter, error handling cleaner, and unit normalization more precise.
How do I choose which API fields belong in the ontology?
Prioritize fields that add context for price analysis, such as commodity codes, currency, units of measurement, and timestamps like updated_at. For futures, include contract-month and front-month identifiers so you can separate specific instruments from continuous price series.
It also helps to include fields that support reporting and transparency, like source descriptors and formatted price strings, while keeping the ontology consistent across commodities in OilpriceAPI.
What should I validate before releasing an ontology-backed commodity API?
Before release, check your technical setup, data quality, and compliance assumptions. Run unit, integration, and performance tests to make sure endpoints return the expected responses and stay within your latency limits.
Also check that your schema matches your SDK assumptions, your data is current and complete enough for the job, and your usage rights are in place. On top of that, make sure error handling, timeouts, and secure authentication work as expected in production.