In the previous tutorial, you built and modified a data contract. In this tutorial, you will see that building a data product is also a matter of only a few API calls.

This tutorial is the first of a series of four, explaining how you can use and get value from Data Contracts & Data Products in a very hands-on way. Over ninety people participated in the early alpha and beta in May and June 2025, with testimonies such as the one from Adam Holley: I believe providing an easy-to-use data product and data contract APIs with well defined, yet flexible, structures will help bring discipline to the data space by allowing data practitioners to focus on content instead of structure of the metadata.

Check out my list of data contracts & data products tutorials on Medium; you’ll find the link to the other three tutorials and the surveys. The associated GitHub repo contains extra information. When you finish the four tutorials and four surveys, you will receive a digital badge to show your recently acquired knowledge.

Let’s start with a little bit of theory, before we jump into the fun stuff

Setting up the playground

For simplicity, as I did previously, I will export all key values as environment variables, so it will be easier to copy/paste the curl calls. Remember, your values are different…

export BITOL_URL=https://cloud.jgp.ai/api
export BITOL_USER_PW=BitolRu7ez!
export BITOL_USER_EMAIL=jgp@jgp.net
export BITOL_API_KEY=97d09209-9021-4b24-89a9-620aae063d40
export BITOL_CONTRACT_ID=34cae6d7-7648-38b2-8f66-8db79e1e2ce4

Reminder: the repository and resources are available at https://github.com/jgpdotai/cloud-services/.

What is a Data Product?

No, I will not fall into the trap of defining a data product. I will assume you have a basic understanding of it. In any case, there is a community definition that everybody agrees with. Wow, I just dodged this bullet.

Standardizing Data Products

We need to standardize data products in order to experiment with them. There are several standards for defining data products, but I will only focus on Bitol’s Open Data Product Standard (ODPS).

A little background: Bitol is a Linux Foundation project that is developing open standards for product-oriented data engineering and management. As of now, Bitol’s flagship deliverable is the Open Data Contract Standard (ODCS), but Bitol’s ambition goes further. Check out our roadmap.

From this perspective, a data product looks like the following figure.

Structure of Bitol’s Open Data Product Standard.

The structure is very similar to ODCS and reuses some of its elements, such as Team, Support, authoritative definitions, tags, custom properties, and more.

The critical thing to remember is that a data product can contain multiple artifacts, each with potentially multiple versions. This structure allows product thinking and lifecycle management.

An example will help: a data product can expose a retailer’s transactions. The first artifact can be the raw transactions as they happen, within a few milliseconds. A second can be the consolidated transactions with a 24-hour delay, while a third could be the transactions with returns after the return period has passed. A version for each artifact will allow development and production versions to be available simultaneously (or multiple production versions to allow a smooth transition).

Using Bitol’s ODPS, the code representation of such an example would look like:

apiVersion: "v1.0.0"
kind: "DataProduct"
name: "Transactions"
id: "7ab7d0ff-d6cf-4310-80c6-81164f9c4a54"
version: "v0.1.0"
description:
purpose: "All RetailCorp's transactions"
status: "active"
artifacts:
- name: "Raw transactions"
versions:
- version: "v1.0.0"
out:
- contractId: "c2798941-1b7e-4b03-9e0d-955b1a872b32"
version: "v0.1.0"
- version: "v2.0.0"
out:
- contractId: "c2798941-1b7e-4b03-9e0d-955b1a872b32"
version: "v2.0.0"
- name: "Consolidated transactions"
versions:
- version: "v1.0.0"
out:
- contractId: "a44978be-1fe0-4226-b840-1b715bc25c63"
- name: "Full transactions with returns"
versions:
- version: "v0.3.0"
out:
- contractId: "ef769969-0cbe-4188-876f-bb00abadaee4"

Let’s build one!

Now that the theory is behind us, let’s build a data product. Once more, creating your first draft is as simple as an API call.

curl -X POST "$BITOL_URL/v1/products?contractId=$BITOL_CONTRACT_ID&contractVersion=0.1.1" 
-H "X-API-KEY: $BITOL_API_KEY"
-H "X-USER-PASSWORD: $BITOL_USER_PW"

Let’s analyze the call:

  • contractId=$BITOL_CONTRACT_ID is the first output contract of the first artifact.
  • contractVersion=0.1.1 specifies a semantic version (semver) number of this data contract.

And you should get:

{
"name": "Default Data Product",
"id": "3153baf0-0c77-351d-b10c-a98578e10806",
"version": "0.1.0",
"status": "draft"
}

You should get the same id as the service is idempotent based on the information we just shared with it. Let’s export the product’s id as we did previously.

export BITOL_PRODUCT_ID=3153baf0-0c77-351d-b10c-a98578e10806

And let’s read the data product we just created.

curl -X GET "$BITOL_URL/v1/products/$BITOL_PRODUCT_ID" 
-H "X-API-KEY: $BITOL_API_KEY"
-H "X-USER-PASSWORD: $BITOL_USER_PW"
--output $BITOL_PRODUCT_ID-0.1.0.odps.yaml

The resulting product is:

apiVersion: "v0.9.0"
kind: "DataProduct"
domain: ""
name: "Default Data Product"
description:
purpose: "Automatically generated data product output contract"
usage: null
limitations: null
id: "3153baf0-0c77-351d-b10c-a98578e10806"
team:
- dateIn: "2025-05-20"
role: "DPO"
name: "Jean-Georges Perrin"
comment: "automatically generated"
username: "jgp@jgp.net"
version: "v0.1.0"
tenant: ""
status: "draft"
artifacts:
- versions:
- version: "v1.0.0"
out:
- contractId: "34cae6d7-7648-38b2-8f66-8db79e1e2ce4"
version: "v0.1.1"
name: "First Artifact of Default Data Product"
artifactId: "d2477927-083c-3029-817e-84791557c91a"

As you did with the contract, you can modify it, enrich it, and then upload it.

curl -X POST "$BITOL_URL/v1/products?version=0.9.0" 
-H "X-API-KEY: $BITOL_API_KEY"
-H "X-USER-PASSWORD: $BITOL_USER_PW"
-F "file=@$BITOL_PRODUCT_ID-0.1.0.odps.yaml"

You can check all your data products using:

curl -X GET "$BITOL_URL/v1/products" 
-H "X-API-KEY: $BITOL_API_KEY"
-H "X-USER-PASSWORD: $BITOL_USER_PW" | jq

You’re Officially a Data Product Builder! 🎉

And just like that, you’ve gone from curious clicker to full-on data product creator! You started with some theory (because, hey, context matters), dropped some env vars like pros, and built an entire data product with nothing more than a few well-aimed API calls and a splash of YAML. I hope you enjoyed this tutorial.

You now have the power to manage artifacts, control versions, and speak ODPS like a native. Not too shabby for what’s essentially a data product LEGO. From raw transactions to full lifecycle governance, you’ve seen how structure and standardization don’t have to be boring — they can be the playground.

But remember: ODPS is still a work in progress — an open standard in active development under the Bitol project at the Linux Foundation. That means your feedback, ideas, and contributions are not just welcome — they’re needed. If you’ve got thoughts, use cases, or just want to join the party, hop over to bitol.io or the GitHub repo and help shape the future.

So go on — build, explore, remix. The Bitolverse is yours.

Updates:

  • 2025–07–31 The tutorial is now public, but still uses an old version of Bitol’s ODPS. Bitol ODPS v0.9.0 has been released after the first draft of this article, and there are a few changes in the syntax, although the philosophy is very similar.


Playing with Data Products was originally published in Data Mesh Learning on Medium, where people are continuing the conversation by highlighting and responding to this story.

Leave a Reply

Your email address will not be published. Required fields are marked *