Skip to main content

Overview

The OTLP metrics endpoint accepts OpenTelemetry Protocol (OTLP) metric data in protobuf or JSON format. Metrics capture time-series data for token usage, costs, and other numerical measurements.

Endpoint

POST /otlp/v1/metrics

Authentication

This endpoint requires Bearer token authentication using an Agent API key.
Include your agent API key in the Authorization header:
Authorization: Bearer mnfst_your_agent_api_key_here
How to get an API key:
  1. Create an agent in the Manifest dashboard or via the API
  2. The agent API key is automatically generated with the mnfst_ prefix
  3. Retrieve it using the Get Agent Key endpoint

Local Mode Authentication

In local mode (MANIFEST_MODE=local), loopback connections (127.0.0.1) bypass authentication. This is useful for development with the OpenClaw plugin in dev mode.

Content Types

The endpoint accepts two content types:
  • application/x-protobuf (recommended) - Binary protobuf format, more efficient
  • application/json - JSON format for easier debugging
Set the Content-Type header accordingly.

Request Body

The request body follows the OTLP metrics specification. The top-level structure is ExportMetricsServiceRequest.

Structure

{
  "resourceMetrics": [
    {
      "resource": {
        "attributes": [
          { "key": "service.name", "value": { "stringValue": "my-agent" } },
          { "key": "agent.name", "value": { "stringValue": "my-agent" } }
        ]
      },
      "scopeMetrics": [
        {
          "scope": {
            "name": "openclaw-instrumentation",
            "version": "1.0.0"
          },
          "metrics": [
            {
              "name": "gen_ai.usage.input_tokens",
              "description": "Number of input tokens consumed",
              "unit": "tokens",
              "sum": {
                "dataPoints": [
                  {
                    "asInt": 1500,
                    "timeUnixNano": "1609459200000000000",
                    "attributes": [
                      { "key": "agent.name", "value": { "stringValue": "my-agent" } }
                    ]
                  }
                ],
                "aggregationTemporality": 2,
                "isMonotonic": true
              }
            },
            {
              "name": "gen_ai.usage.cost",
              "description": "Cost in USD",
              "unit": "USD",
              "gauge": {
                "dataPoints": [
                  {
                    "asDouble": 0.045,
                    "timeUnixNano": "1609459200000000000"
                  }
                ]
              }
            }
          ]
        }
      ]
    }
  ]
}

Recognized Metrics

Manifest automatically processes specific metric names:

Token Usage Metrics

Metric NameTypeDescription
gen_ai.usage.input_tokensSumNumber of input tokens consumed
gen_ai.usage.output_tokensSumNumber of output tokens generated
gen_ai.usage.total_tokensSumTotal tokens (input + output)
gen_ai.usage.cache_read_tokensSumCached input tokens read
gen_ai.usage.cache_creation_tokensSumTokens used for cache creation

Cost Metrics

Metric NameTypeDescription
gen_ai.usage.costGaugeCost in USD
gen_ai.cost.usdGaugeCost in USD (alternative name)
Note: Only the above metrics are processed. Other metric names are ignored.

Attributes

Resource Attributes

AttributeTypeDescription
service.namestringService identifier (fallback for agent name)
agent.namestringAgent name (overrides service.name)

Data Point Attributes

AttributeTypeDescription
agent.namestringAgent name (overrides resource attribute)

Response

partialSuccess
object
required
Partial success information (omitted if all data points accepted)
{
  "partialSuccess": {}
}

Examples

cURL with JSON

curl -X POST https://manifest.build/otlp/v1/metrics \
  -H "Authorization: Bearer mnfst_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "resourceMetrics": [{
      "resource": {
        "attributes": [
          { "key": "service.name", "value": { "stringValue": "my-agent" } }
        ]
      },
      "scopeMetrics": [{
        "scope": { "name": "my-instrumentation" },
        "metrics": [
          {
            "name": "gen_ai.usage.input_tokens",
            "sum": {
              "dataPoints": [{
                "asInt": 1500,
                "timeUnixNano": "1609459200000000000"
              }],
              "aggregationTemporality": 2,
              "isMonotonic": true
            }
          },
          {
            "name": "gen_ai.usage.output_tokens",
            "sum": {
              "dataPoints": [{
                "asInt": 300,
                "timeUnixNano": "1609459200000000000"
              }],
              "aggregationTemporality": 2,
              "isMonotonic": true
            }
          },
          {
            "name": "gen_ai.usage.cost",
            "gauge": {
              "dataPoints": [{
                "asDouble": 0.025,
                "timeUnixNano": "1609459200000000000"
              }]
            }
          }
        ]
      }]
    }]
  }'

OpenTelemetry SDK (Node.js)

import { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-http';
import { MeterProvider, PeriodicExportingMetricReader } from '@opentelemetry/sdk-metrics';
import { Resource } from '@opentelemetry/resources';

const exporter = new OTLPMetricExporter({
  url: 'https://manifest.build/otlp/v1/metrics',
  headers: {
    'Authorization': 'Bearer mnfst_your_api_key'
  }
});

const meterProvider = new MeterProvider({
  resource: Resource.default().merge(new Resource({
    'service.name': 'my-agent',
    'agent.name': 'my-agent'
  })),
  readers: [
    new PeriodicExportingMetricReader({
      exporter,
      exportIntervalMillis: 10000  // Export every 10 seconds
    })
  ]
});

const meter = meterProvider.getMeter('my-instrumentation');

const inputTokensCounter = meter.createCounter('gen_ai.usage.input_tokens', {
  description: 'Number of input tokens consumed',
  unit: 'tokens'
});

const outputTokensCounter = meter.createCounter('gen_ai.usage.output_tokens', {
  description: 'Number of output tokens generated',
  unit: 'tokens'
});

const costGauge = meter.createObservableGauge('gen_ai.usage.cost', {
  description: 'Cost in USD',
  unit: 'USD'
});

// Record metrics
inputTokensCounter.add(1500);
outputTokensCounter.add(300);

Protobuf Format

When using application/x-protobuf, the request body must be a binary-encoded protobuf message following the OTLP metrics specification. Protobuf message definition:
message ExportMetricsServiceRequest {
  repeated ResourceMetrics resource_metrics = 1;
}

message ResourceMetrics {
  Resource resource = 1;
  repeated ScopeMetrics scope_metrics = 2;
}

message ScopeMetrics {
  InstrumentationScope scope = 1;
  repeated Metric metrics = 2;
}

message Metric {
  string name = 1;
  string description = 2;
  string unit = 3;
  oneof data {
    Gauge gauge = 5;
    Sum sum = 7;
  }
}

message Gauge {
  repeated NumberDataPoint data_points = 1;
}

message Sum {
  repeated NumberDataPoint data_points = 1;
  AggregationTemporality aggregation_temporality = 2;
  bool is_monotonic = 3;
}

message NumberDataPoint {
  repeated KeyValue attributes = 7;
  fixed64 start_time_unix_nano = 2;
  fixed64 time_unix_nano = 3;
  oneof value {
    double as_double = 4;
    sfixed64 as_int = 6;
  }
}

enum AggregationTemporality {
  AGGREGATION_TEMPORALITY_UNSPECIFIED = 0;
  AGGREGATION_TEMPORALITY_DELTA = 1;
  AGGREGATION_TEMPORALITY_CUMULATIVE = 2;
}
See the OpenTelemetry Protocol Specification for the complete protobuf schema.

Data Processing

When metrics are ingested, Manifest:
  1. Decodes the protobuf or JSON payload
  2. Filters metrics by name (only token and cost metrics are processed)
  3. Extracts data points and attributes
  4. Stores metrics in the database:
    • Token metrics → token_usage_snapshots table
    • Cost metrics → cost_snapshots table
  5. Emits real-time events to connected dashboard clients via SSE

Data Point Mapping

Each data point is stored with:
  • tenant_id - Extracted from the authenticated agent API key
  • agent_id - Extracted from the authenticated agent API key
  • agent_name - From resource or data point attributes
  • snapshot_time - Timestamp from timeUnixNano
  • Metric-specific fields (e.g., input_tokens, output_tokens, cost_usd)

Rate Limiting

OTLP endpoints use in-memory API key caching (5-minute TTL) to minimize database lookups. Each successful authentication is cached for 5 minutes.

Error Handling

Status CodeDescription
200Success (all data points accepted)
401Unauthorized (invalid or expired API key)
415Unsupported Media Type (invalid Content-Type)
500Internal Server Error

Use Cases

Real-Time Token Tracking

Send token usage metrics after each LLM call to track consumption in real-time:
// After making an LLM call
const usage = response.usage;
inputTokensCounter.add(usage.input_tokens);
outputTokensCounter.add(usage.output_tokens);

Cost Monitoring

Send cost metrics to track spending:
const cost = calculateCost(usage, modelPricing);
costGauge.record(cost);

Dashboard Analytics

Metrics are aggregated and displayed in the Manifest dashboard:
  • Token usage over time (hourly/daily/weekly)
  • Cost trends and breakdowns
  • Per-agent usage statistics

See Also

Build docs developers (and LLMs) love