Python SDK for the Ouro API

Last updated 14d ago

Learn how to interact with Ouro from Python.

Before working with any of the methods outlined below, you'll need to initialize the Ouro client. Make sure you've updated ouro-py to the latest version as we are consistently making updates. More details can be found in the quickstart.

from ouro import Ouro
 
ouro = Ouro(api_key=os.environ.get("OURO_API_KEY"))

Assets

There are four categories of assets on Ouro. Starting from the most fundamental, there are files, datasets, APIs, posts, and conversations.

For every asset on the platform, Ouro stores the following information:

  • ID
  • Name
  • Description (optional)
  • Metadata (asset specific details, like file size or number of rows)
  • Visibility (public, private, monetized, or organization)
  • Created at
  • Updated at
  • Owner
  • Organization
  • Team
  • Price (if monetized)

Unless specifically working in an organizational context, assets are created within a global organization that includes all users on Ouro. You can control who sees the asset by adjusting the visibility.

Choosing the right team for an asset helps track a team's activity and make sure the right people find the right assets.

Files

Files are the most basic asset on Ouro. Just like the files on your computer, any file is fair game. Many files types have rich visualizations on the web platform.

Create a file

You can upload any file, up to 5GB in size.

file = ouro.files.create(
    name="cif file",
    description="Test file",
    visibility="public",
    file_path="./Fe.cif",
)
print(file)

file_path is the path to the file on your local machine.

If you need to upload multiple files at once, currently the best way is to compress them into a .zip file and upload that.

If you run into issues uploading large files, you can upload them using the web interface. After upload, you'll be able to find the file ID in the details dropdown (cog icon) on the file page header. Once you have the file ID, you can use it to interact with the file programmatically.

Read a file

id = '48ec6563-5520-4756-ac7f-b38f4933ac95'
file = ouro.files.retrieve(id)
 
# file is an object with properties like:
# file.id, file.name, file.description, file.metadata, file.visibility, file.created_at, file.user

If you created the file from the SDK, the ouro.files.create response will be a file object which has the ID in the id field. If you uploaded the file using the web interface, or the asset was created by another user, you can find its ID in the file page header details dropdown (cog icon).

See the highlighted section in the screenshot below:

File ID in the details dropdown

Once you've retrieved a file object with ouro.files.retrieve, you can read its data using the read_data method.

file_data = file.read_data()
print(file_data.url)

The read_data method returns a FileData object which has a url property that you can use to download the file.

import requests
 
url = file_data.url
response = requests.get(url)
print(response.content)

Update a file

To be able to update a file, you must have admin or write permission on the file. As the creator of a file, you are automatically granted admin permissions.

file_id = file.id
updated_file = ouro.files.update(
    id=file_id,
    file_path="./Fe2BiNi.cif",
    name="Fe2BiNi (Pmmm) updated"
)
# Returns the updated file object
print(updated_file)

You can update the file object with any of asset properties using named parameters. Only id is required. file_path is a special property for files that allows you to update the file data with a new file. This is also optional.

Delete a file

You must have admin permissions on the file to delete it. This will completely remove the file from the platform.

ouro.files.delete(id=file.id)

Assets that may have referenced the file will no longer show a connection to the file.

Datasets

Datasets are structured tabular assets stored in the datasets schema. You can create them from a pandas DataFrame, then retrieve metadata, read the schema, and load/query the data.

Create a dataset

Provide a pandas DataFrame and required asset fields. The SDK will infer a SQL schema and upload a preview; if you pass data, rows are inserted into the table.

import pandas as pd
 
data = pd.DataFrame(
    [
        {"name": "Bob", "age": 30},
        {"name": "Alice", "age": 27},
        {"name": "Matt", "age": 26},
        {"name": "Bobo", "age": 4},
        {"name": "Asta", "age": 15},
    ]
)
 
dataset = ouro.datasets.create(
    name="preview-dataset",
    visibility="public",
    monetization="none",
    data=data,
)
print(dataset.id)

Notes:

  • name is converted to a SQL-safe table_name (spaces -> underscores, lowercase) stored in dataset.metadata["table_name"].
  • If you omit data, the dataset (asset + empty table) is created without rows.

Read a dataset

Retrieve the dataset object by ID to access standard asset fields plus dataset-specific metadata and preview rows.

dataset_id = "0194f68c-b16e-70d3-8ed3-aafa850272ae"
dataset = ouro.datasets.retrieve(dataset_id)
print(dataset.name, dataset.metadata, dataset.preview[:3])

Read schema

Get column definitions for the underlying table.

columns = ouro.datasets.schema(dataset_id)
for col in columns:
    print(col["column_name"], col["data_type"])  # e.g., age integer, name text

Query data by dataset ID

Fetch the dataset's rows as a pandas DataFrame via the Ouro API. Timestamp and date columns are parsed to pandas types.

df = ouro.datasets.query(dataset_id)
print(df.head())

Load data by table name (fast for large datasets)

Directly read from the datasets schema using Supabase; this may be faster for large tables and will batch when >1,000,000 rows.

table_name = dataset.metadata["table_name"]  # e.g., "preview-dataset"
df = ouro.datasets.load(table_name)
print(len(df))

Update a dataset

Update asset properties and optionally write rows to the table by passing a DataFrame. Writing new data will replace the existing data in the table.

updated = ouro.datasets.update(
    dataset.id,
    visibility="private",
)
 
# Write rows (optional)
data_update = pd.DataFrame([
    {"name": "Charlie", "age": 33},
])
updated = ouro.datasets.update(dataset.id, data=data_update)

Delete a dataset

Requires admin permission on the asset.

ouro.datasets.delete(dataset.id)

Tips:

  • Visibility controls who can access the dataset: public, private, monetized, or organization.
  • For IDs created in the web UI, find the ID in the dataset page header details dropdown.

Services

External APIs on Ouro are organized with two asset types: services and routes.

A service is a collection of routes that all share the same base URL. Routes are individual endpoints of the API defined by a HTTP method, path, and optional URL and query parameters and a body.

Ouro makes no guarantees about the stability of the APIs added to the platform. Make sure you trust the source/creator of the API before sending any sensitive data. You don't need to worry about the security of your account as no Ouro credentials are shared with the API.

If the API endpoint is monetized, you'll only be charged for successful requests.

Create a service

Not yet supported from the SDK. Use the web interface to create a service.

You can learn more about creating services with our guide How to monetize APIs. While focused on monetizing existing APIs, it covers the basics of creating and adding an API to Ouro.

Read a service

service_id = "438e454b-cf9e-40d9-b53d-b9b250087179"
service = ouro.services.retrieve(service_id)

Like the file object, using ouro.services.retrieve will return a service object with the same base asset properties.

Once you've retrieved a service object, you can interact with routes property.

# Returns a list of Route objects
service_routes = service.read_routes()
 
# Returns a dictionary with the stored OpenAPI specification as JSON
service_spec = service.read_spec()
print(service_spec['info']['title'])

The read_spec() method retrieves the stored OpenAPI specification from our database. This is the parsed specification that was either uploaded or fetched from a remote URL when the service was created. The spec is stored as a JSON object and remains stable even if the original remote spec changes, until the service is updated.

You can use the functionality of a service's endpoint with the use_route method.

route = service_routes[0]
response = service.use_route(
    route.id,
    body={"composition": "Fe2Ni", "temperature": 0.8, "max_new_tokens": 3000}
)
# Returns a dictionary with the response from the endpoint

More details on using route functionality is in the routes section.

Update a service

Not yet supported from the SDK. Use the web interface to update a service. Navigate the asset and click the edit icon in the asset's header. You'll be able to adjust things like base URL, name, description, and authentication.

To update an individual route, navigate to the route and click the edit button to use the edit form.

Delete a service

Not yet supported from the SDK. Use the web interface to delete a service. Navigate the asset and click the delete button in the settings dropdown in the asset's header.

Routes

Routes are the individual endpoints of a service. These represents a single HTTP endpoint of the underlying web API.

Create a route

Not yet supported from the SDK. Use the web interface to create a route. Navigate the service you want to add the route to and click the Add Route button above the service's route table.

Read a route

To get the details of a route, use the ouro.routes.retrieve method. You can supply either the route ID or the asset identifier which consists of the creator and the route name + method.

The identifier is the section of the URL after routes/. Unless you own the route, it's best practice to use the ID as it will remain fixed even if the name of the asset changes. When the name of an asset changes, it's identifier and URL will also change.

route_id = "2443b425-a2bf-4a6f-8202-3ba8b36c921f" # CrystaLLM generate route
route = ouro.routes.retrieve(route_id)
 
# OR
 
route_identifier = "mmoderwell/post-generate"
route = ouro.routes.retrieve(route_identifier)
 
# Returns a Route object

Update a route

Not yet supported from the SDK. Use the web interface to update a route. Navigate the route and click the edit button to use the edit form.

Delete a route

Not yet supported from the SDK. Use the web interface to delete a route.

Use a route

You can use the functionality of a route with the use method.

route = ouro.routes.retrieve("mmoderwell/post-generate")
 
# Returns a dictionary with the response from the endpoint
generation = route.use(
    body={
       "composition": "Fe2Ni", "temperature": 0.8, "max_new_tokens": 3000
    }
)

Other supported parameter to the use method are:

  • parameters: a dictionary of URL parameters to pass to the endpoint
  • input_asset: an asset object to pass to the endpoint

You can chain together multiple routes to create custom workflows. Many routes create assets like files or datasets as output. The Python SDK will automatically add the asset's data to the route response.

generation_route = ouro.routes.retrieve("mmoderwell/post-generate")
generation = generation_route.use(
    body={
       "composition": "Fe2Ni", "temperature": 0.8, "max_new_tokens": 3000
    }
)
 
# Get the file id from the generation. Added as a file object in dictionary format.
file_id = generation["file"]["id"]
 
prediction_route = ouro.routes.retrieve("mmoderwell/post-magnetism-curie-temperature")
prediction = prediction_route.use(
    input_asset={
        "assetId": file_id,
        "assetType": "file"
    }
)
print(prediction)

Read a route's actions

Every time a route is used, Ouro will store the request and response in an action object. Actions help keep track of what inputs created what outputs, including connections to the assets that may have been created by using a route.

actions = route.read_actions()
# Returns a list of dictionary objects with the action details
print(actions)

Actions are especially useful for long running routes. If the route takes longer than 10 minutes to complete, waiting for a response can be troublesome. Actions allow developers to poll the status of a route use and retrieve the response when it's ready.

Posts

Posts on Ouro are a way to share rich text-focused content with the Ouro community. Just like with every other kind of asset, you can create posts with a text editor on the web interface or use the SDK to create them programmatically.

The SDK exposes an Editor class that allows you to construct a document block by block. Blocks are things like paragraphs, headings, lists, or references to other assets.

import pandas as pd
 
content = ouro.posts.Editor()
 
content.new_header(level=1, text="Hello World")
content.new_paragraph(text="This is a paragraph")
content.new_code_block(language="python", code="print('Hello, World!')")
content.new_table(pd.DataFrame({"A": [1, 2, 3], "B": [4, 5, 6]}))
content.new_inline_asset(id="438e454b-cf9e-40d9-b53d-b9b250087179", asset_type="service", view_mode="default")
content.new_inline_asset(id="8891046c-b52c-432f-b9b0-ca9515bb1c20", asset_type="file", view_mode="chart")

Valid methods of the Editor class are:

  • new_header: adds a header block of specified level (1-3)
  • new_paragraph: adds a paragraph of text
  • new_code_block: adds a code block
  • new_table: adds a table from a pandas DataFrame
  • new_inline_asset: adds an Ouro asset with an optional visualization of the data (view_mode="chart")

Using the editor only creates a local object with your content. You can view your content as JSON and Markdown text using the to_dict method.

Create a post

Before you can create a post, you need to have the content you want to post. The Editor class explained above is one way, or you can use Ouro utils to convert a markdown string to a Content object.

markdown_string = """
# Hello World
This is a test post from the Python SDK
"""
 
conversion = ouro.client.post(
    "/utilities/convert/from-markdown", json={"markdown": markdown_string}
).json()
content = ouro.posts.Editor(text=markdown_string, json=conversion["json"])

This utility is useful for converting the output of an LLM. You can instruct the LLM to output an Ouro-extended version of Markdown with a prompt like the following:

Write your responses in extended markdown, using additional syntax when referencing Ouro users and assets. For users, inline a reference with inline code: `@{username}` For assets (files, datasets, posts, routes, services), use a codeblock with a JSON config:

```assetComponent
{
"id": string uuid,
"assetType": 'post' | 'file' | 'dataset' | 'route' | 'service',
"viewMode": 'chart' | 'default'
}
```

When you embed an asset, you don't need to provide the title, description, or link as it will be done in the embed. For files and datasets, prefer the chart view mode, otherwise default.

Once you have your content, you save it to Ouro with the ouro.posts.create method.

post = ouro.posts.create(
    content=content,
    name="Hello World",
    description="This is a post from the Python SDK",
    visibility="public",
)
# Returns a Post object
print(post)

Note: If you start your content with an H1, the name of the post should match the H1.

Read a post

You can read a post with the ouro.posts.retrieve method. You can find the ID of a post in the response of the ouro.posts.create method or from the web interface.

post_id = "0190ea44-bfef-7f8b-9e5f-503fc20a4d91"
post = ouro.posts.retrieve(post_id)
 
# Returns a Post object
print(post)

You will find the post's content in the content property of the post object. You can get a markdown representation of the content with the text property.

post_markdown = post.content.text
print(post_markdown)

Update a post

You can update a post with the ouro.posts.update method.

updated_post = ouro.posts.update(
    post.id,
    description="This is a post from the Python SDK that is now private",
    visibility="private"
)
 
# Returns the updated post object
print(updated_post)

You can update any of the post's properties, including the content, using the same approach used to create it.

Delete a post

You can delete a post with the ouro.posts.delete method. You must be an admin of the post to delete it.

ouro.posts.delete(post.id)

Conversations

Conversations let users exchange messages. You can list and retrieve conversations, update their properties, and create/list messages within a conversation.

List conversations

Start by listing your conversations to find threads you want to work with.

conversations = ouro.conversations.list()
for c in conversations:
    print(c.id, c.name, c.metadata)

Retrieve a conversation

Once you have an ID, load the conversation to inspect metadata and access message helpers.

conversation_id = "0190ea44-bfef-7f8b-9e5f-503fc20a4d91"
conversation = ouro.conversations.retrieve(conversation_id)
print(conversation.name, conversation.metadata)

Update a conversation

You can update top-level fields like name and description.

updated = ouro.conversations.update(
    conversation_id,
    name="Project Alpha",
    description="Research thread"
)
print(updated)

Create a message

Send a message to a conversation as plain text or structured JSON. Use the conversation.messages.create helper.

# Text message
msg = conversation.messages.create(text="Hello team! 👋")
 
# JSON message (rich content)
editor = ouro.posts.Editor()
editor.new_paragraph(text="Hello team! 👋")
msg2 = conversation.messages.create(json=editor.to_dict())

List messages

Read the latest messages to understand the current context of the thread.

messages = conversation.messages.list()
for m in messages:
    print(m.get("id"), m.get("text") or m.get("json"))

Creating or deleting conversations is not yet supported in the SDK; use the web interface.

Comments

Comments are lightweight, rich-text notes attached to any asset (files, datasets, posts, routes, services, conversations). One-level threads are supported: top-level comments on an asset, and replies to those comments. Use the built-in Editor to compose content, then create, list, and update comments.

Create a comment

Compose with the Editor, then create a comment on a parent asset by ID.

parent_asset_id = "0190ea44-bfef-7f8b-9e5f-503fc20a4d91"  # can be any asset id
 
editor = ouro.comments.Editor()
editor.new_paragraph(text="Great post! I especially liked the dataset example.")
 
comment = ouro.comments.create(
    parent_id=parent_asset_id,
    content=editor,
    visibility="public",
)
print(comment.id)

List comments for an asset

Fetch all top-level comments attached to an asset.

comments = ouro.comments.list_by_parent(parent_asset_id)
for c in comments:
    # The content is stored as text + JSON structure
    print(c.id, c.content.text)

Retrieve a comment

fetched = ouro.comments.retrieve(comment.id)
print(fetched.content.text)

Update a comment

Rebuild the content with Editor or pass an updated Content object.

update_editor = ouro.comments.Editor()
update_editor.new_paragraph(text="Edited: adding one more note.")
 
updated_comment = ouro.comments.update(
    comment.id,
    content=update_editor,
    visibility="private",
)
print(updated_comment)

Create and list replies

Replies are simply comments whose parent is a comment. Only one level of replies is supported.

# Create a reply to a top-level comment
reply_editor = ouro.comments.Editor()
reply_editor.new_paragraph(text="Replying here with more details.")
 
reply = ouro.comments.create(
    parent_id=comment.id,  # parent is the comment id
    content=reply_editor,
    visibility="public",
)
 
# List replies for a top-level comment
replies = ouro.comments.list_replies(comment.id)
for r in replies:
    print(r.id, r.content.text)