> ## Documentation Index
> Fetch the complete documentation index at: https://docs.unomiq.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Using the Economics Engine API

> Learn how to query traces, retrieve metrics, and inspect spans from the Unomiq Economic Engine API using the unomiq-sdk

This guide describes how to use the [`unomiq-sdk`](https://pypi.org/project/unomiq-sdk/) Python package to query the [Unomiq Economic Engine API](/api-reference/engine/introduction) — the API that lets you search traces, retrieve aggregated metrics, list traces, and inspect individual spans.

***

## Prerequisites

### API Credentials

Create API credentials from the [Unomiq Dashboard](https://dashboard.unomiq.com). The credentials must have the `read:traces` permission.

This will give you an API key (**Client ID**) and secret (**Client Secret**).

### Application ID

You need the **Application ID** (`app_id`) for the application whose traces you want to query. You can find this in the [Unomiq Dashboard](https://dashboard.unomiq.com) under your application settings.

### Install the SDK

```bash theme={null}
pip install unomiq-sdk
```

***

## Configuration

Set the following environment variables for your application:

| Variable               | Description                          |
| ---------------------- | ------------------------------------ |
| `UNOMIQ_CLIENT_ID`     | API Key from the Unomiq Dashboard    |
| `UNOMIQ_CLIENT_SECRET` | API Secret from the Unomiq Dashboard |

The SDK uses these credentials internally to acquire and refresh OAuth2 tokens when making requests to the Engine API.

Alternatively, you can pass credentials directly to the constructor:

```python theme={null}
from unomiq import UnomiqEconomicsEngine

engine = UnomiqEconomicsEngine(
    app_id="my-app-id",
    client_id="your-client-id",
    client_secret="your-client-secret",
)
```

### Constructor Parameters

| Parameter       | Type          | Default                              | Description                                                         |
| --------------- | ------------- | ------------------------------------ | ------------------------------------------------------------------- |
| `app_id`        | `str`         | **required**                         | Application ID (sent as `X-Unomiq-App-Id` header)                   |
| `client_id`     | `str \| None` | `None`                               | OAuth2 client ID (falls back to `UNOMIQ_CLIENT_ID` env var)         |
| `client_secret` | `str \| None` | `None`                               | OAuth2 client secret (falls back to `UNOMIQ_CLIENT_SECRET` env var) |
| `api_endpoint`  | `str`         | `https://engine-api.unomiq.com`      | Engine API endpoint                                                 |
| `token_url`     | `str`         | `https://oauth-api.unomiq.com/token` | OAuth2 token endpoint                                               |
| `scopes`        | `str`         | `read:traces`                        | OAuth2 scopes                                                       |
| `audience`      | `str`         | `https://engine-api.unomiq.com`      | OAuth2 audience                                                     |

***

## Initializing the Client

Using environment variables (recommended):

```python theme={null}
from unomiq import UnomiqEconomicsEngine

engine = UnomiqEconomicsEngine(app_id="my-app-id")
```

<Note>
  Remember to call `engine.close()` when you are done to release resources.
</Note>

***

## Querying Spans

The [`engine/query`](/api-reference/engine/query-traces-spans) endpoint lets you search for individual spans across traces using filter expressions.

```python theme={null}
from datetime import datetime
from unomiq import UnomiqEconomicsEngine, Condition, QueryOperator

engine = UnomiqEconomicsEngine(app_id="my-app-id")

results = engine.query(
    start_time=datetime(2026, 1, 15),
    end_time=datetime(2026, 1, 16),
    filter=Condition(field="service_name", op=QueryOperator.EQ, value="my-service"),
)

for trace in results.data:
    print(f"{trace.trace_id} — {trace.span_name} ({trace.duration_ms}ms)")

engine.close()
```

The response includes paginated span data along with the total count of matching results.

***

## Filter Expressions

The SDK provides composable filter primitives — `Condition`, `AndGroup`, and `OrGroup` — that map directly to the Engine API filter schema. These can be used with `engine.query()`, `engine.get_traces()`, and `engine.get_metrics()`.

### Simple Condition

A `Condition` matches a single field against a value using a comparison operator:

```python theme={null}
from unomiq import Condition, QueryOperator

filter = Condition(field="service_name", op=QueryOperator.EQ, value="my-service")
```

### Supported Operators

| Operator                    | Description              |
| --------------------------- | ------------------------ |
| `QueryOperator.EQ`          | Equal to                 |
| `QueryOperator.NEQ`         | Not equal to             |
| `QueryOperator.CONTAINS`    | Contains substring       |
| `QueryOperator.STARTS_WITH` | Starts with              |
| `QueryOperator.ENDS_WITH`   | Ends with                |
| `QueryOperator.GT`          | Greater than             |
| `QueryOperator.LT`          | Less than                |
| `QueryOperator.GTE`         | Greater than or equal to |
| `QueryOperator.LTE`         | Less than or equal to    |

### Filterable Fields

| Field                   | Description                                                                   |
| ----------------------- | ----------------------------------------------------------------------------- |
| `trace_id`              | Trace identifier                                                              |
| `span_id`               | Span identifier                                                               |
| `span_name`             | Span name                                                                     |
| `span_kind`             | Span kind (e.g., `SERVER`, `CLIENT`)                                          |
| `service_name`          | Service name                                                                  |
| `unit`                  | Unit (equivalent to `span_attributes.unomiq.unit`)                            |
| `parent_unit`           | Parent unit (equivalent to `span_attributes.unomiq.parent_unit`)              |
| `duration_ms`           | Span duration in milliseconds                                                 |
| `span_attributes.*`     | Any span attribute (e.g., `span_attributes.http.method`)                      |
| `resource_attributes.*` | Any resource attribute                                                        |
| `cost`                  | Matched cost                                                                  |
| `billing.resource_type` | Billing resource type (e.g., `db_job`, `api_request`, `llm_generate_content`) |
| `billing.resource_name` | Billing resource name                                                         |
| `billing.service_id`    | Billing service ID                                                            |
| `billing.service_name`  | Billing service name                                                          |
| `billing.sku_id`        | Billing SKU ID                                                                |
| `billing.sku_name`      | Billing SKU name                                                              |
| `billing.region`        | Billing region                                                                |
| `usage.amount`          | Usage amount                                                                  |
| `usage.unit`            | Usage unit                                                                    |

### Combining Conditions with AND / OR

Use `AndGroup` and `OrGroup` to build complex filter expressions:

```python theme={null}
from unomiq import AndGroup, OrGroup, Condition, QueryOperator

filter = AndGroup(and_=[
    Condition(field="span_attributes.http.status_code", op=QueryOperator.GTE, value="200"),
    Condition(field="span_attributes.http.status_code", op=QueryOperator.LTE, value="299"),
    OrGroup(or_=[
        Condition(field="span_kind", op=QueryOperator.EQ, value="SERVER"),
        Condition(field="span_kind", op=QueryOperator.EQ, value="CLIENT"),
    ]),
])

results = engine.query(
    start_time=datetime(2026, 1, 15),
    end_time=datetime(2026, 1, 16),
    filter=filter,
)
```

Groups can be nested arbitrarily to express any boolean logic.

***

## Listing Traces

The [`/traces`](/api-reference/engine/get-traces) endpoint returns a paginated list of traces with aggregated information (duration, span count, total cost).

```python theme={null}
traces = engine.get_traces(
    start_time=datetime(2026, 1, 15),
    end_time=datetime(2026, 1, 16),
)

for t in traces.data:
    print(f"{t.trace_id} — {t.span_name} ({t.spans_count} spans, ${t.total_cost})")
```

By default, filters apply only to root spans. Set `search_child_spans=True` to apply filters across all spans in the trace:

```python theme={null}
traces = engine.get_traces(
    start_time=datetime(2026, 1, 15),
    end_time=datetime(2026, 1, 16),
    search_child_spans=True,
    filter=Condition(field="service_name", op=QueryOperator.EQ, value="my-service"),
)
```

***

## Retrieving a Single Trace

The [`/traces/{trace_id}`](/api-reference/engine/get-trace) endpoint returns all spans for a specific trace:

```python theme={null}
spans = engine.get_trace(
    trace_id="4bf92f3577b34da6a3ce929d0e0e4736",
    start_time=datetime(2026, 1, 15),
    end_time=datetime(2026, 1, 16),
)

for span in spans:
    print(f"  {span.span_id} {span.span_name} ({span.span_kind})")
```

***

## Getting Metrics

The [`/engine/metrics`](/api-reference/engine/get-trace-metrics) endpoint returns aggregated statistical distributions for trace duration, cost, and spans per trace.

```python theme={null}
metrics = engine.get_metrics(
    start_time=datetime(2026, 1, 15),
    end_time=datetime(2026, 1, 16),
)

print(f"Traces: {metrics.trace_count}")
print(f"Duration p50: {metrics.duration.p50}ms, p99: {metrics.duration.p99}ms")
print(f"Cost mean: {metrics.cost.mean}")
```

You can pass the same filter expressions to narrow down the metrics:

```python theme={null}
metrics = engine.get_metrics(
    start_time=datetime(2026, 1, 15),
    end_time=datetime(2026, 1, 16),
    filter=Condition(field="service_name", op=QueryOperator.EQ, value="my-service"),
)
```

The response includes distribution statistics (mean, median, p50, p90, p95, p99, min, max, stddev) for each metric.

***

## Live Traces

The [`/traces/live`](/api-reference/engine/get-live-traces) endpoint returns the most recent spans from the live data stream:

```python theme={null}
live = engine.get_live_traces(
    start_time=datetime(2026, 1, 15),
    end_time=datetime(2026, 1, 16),
)

for span in live.data:
    print(f"{span.trace_id}/{span.span_id} — {span.span_name}")
```

***

## Cleanup

Always close the engine client when you are done to release the underlying HTTP connection and OAuth2 token manager:

```python theme={null}
engine.close()
```

***

## Full Example

Here is a complete example that initializes the client, queries spans, retrieves metrics, and lists traces:

```python theme={null}
from datetime import datetime
from unomiq import (
    UnomiqEconomicsEngine,
    Condition,
    AndGroup,
    OrGroup,
    QueryOperator,
)

engine = UnomiqEconomicsEngine(app_id="my-app-id")

# Query spans for a specific service
results = engine.query(
    start_time=datetime(2026, 1, 15),
    end_time=datetime(2026, 1, 16),
    filter=AndGroup(and_=[
        Condition(field="service_name", op=QueryOperator.EQ, value="my-service"),
        Condition(field="duration_ms", op=QueryOperator.GTE, value="100"),
    ]),
)
print(f"Found {results.total} matching spans")

for span in results.data:
    print(f"  {span.trace_id} — {span.span_name} ({span.duration_ms}ms)")

# Get aggregated metrics
metrics = engine.get_metrics(
    start_time=datetime(2026, 1, 15),
    end_time=datetime(2026, 1, 16),
)
print(f"\nTotal traces: {metrics.trace_count}")
print(f"Duration p50: {metrics.duration.p50}ms, p99: {metrics.duration.p99}ms")
print(f"Cost mean: ${metrics.cost.mean:.4f}")

# List traces with cost information
traces = engine.get_traces(
    start_time=datetime(2026, 1, 15),
    end_time=datetime(2026, 1, 16),
)
for t in traces.data:
    print(f"  {t.trace_id} — {t.span_name} ({t.spans_count} spans, ${t.total_cost})")

# Inspect a specific trace
if traces.data:
    spans = engine.get_trace(
        trace_id=traces.data[0].trace_id,
        start_time=datetime(2026, 1, 15),
        end_time=datetime(2026, 1, 16),
    )
    print(f"\nSpans in trace {traces.data[0].trace_id}:")
    for span in spans:
        print(f"  {span.span_id} {span.span_name} ({span.span_kind})")

engine.close()
```
