Skip to content

Quickstart

This walkthrough creates a run, logs a hyperparameter and a few loss values, and closes the run. It uses the REST transport and curl so there is nothing to install beyond jq; the same calls over gRPC follow at the end.

You need an API token (portal user menu → Get API token) and a workspace you are a member of.

The API token names the deployment it belongs to, so read the base URL out of it, then exchange it for an access token (see Authentication for the details and for refreshing):

Terminal window
export METRANA_API_KEY="…" # from the portal
export METRANA_URL=$(printf '%s' "$METRANA_API_KEY" | tr '_-' '/+' | base64 -d | jq -r .url)
export ACCESS_TOKEN=$(curl -s -X POST "$METRANA_URL/api/management/v1/tokens/exchange" \
-H "Content-Type: application/json" \
-d "{\"api_token\": \"$METRANA_API_KEY\"}" | jq -r .access_token)
Terminal window
curl -s -X POST "$METRANA_URL/api/ingest/v1/runs" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"run": {
"workspace": "my-workspace",
"project": "api-quickstart",
"runName": "run-001",
"experiment": "baseline"
},
"createProjectIfMissing": true
}'

A successful call returns {} (the response’s only field, operationId, is currently always empty, and empty fields are omitted from JSON). The project and the experiment are created along with the run if they do not exist; the workspace must already exist.

Calling CreateRun again with the same name returns 409 Conflict — run names are unique within a project.

UpdateRuns addresses the run by name and carries any mix of attributes and series:

Terminal window
curl -s -X POST "$METRANA_URL/api/ingest/v1/runs:update" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"sessionId": "1",
"runUpdates": [{
"workspace": "my-workspace",
"project": "api-quickstart",
"runName": "run-001",
"attributes": [
{"path": "config/learning_rate", "floatValue": 0.0003},
{"path": "config/optimizer", "stringValue": "adamw"},
{"path": "sys/tags", "stringSetDeltaValue": {"addedValues": ["quickstart"]}}
],
"series": [{
"metricName": "train/loss",
"ordering": "ORDERING_MODE_ABSOLUTE_ORDERED",
"pointBatches": [{
"firstStep": "0",
"values": [2.31, 1.87, 1.52, 1.34, 1.21],
"timestampMillis": "1759233600000"
}]
}]
}]
}'

What the fields mean:

  • sessionId identifies this writer process. Any non-zero number works, but use a fresh one each time your process starts. 64-bit integers are strings in JSON (plain numbers are accepted too).
  • Attributes overwrite by path. sys/tags is one of only two writable sys/ attributes (the other is sys/description).
  • The series is identified by metricName plus its scale and labels, both omitted here (the default scale, ML_STEP, and no labels).
  • The batch holds five points at steps 0–4 (firstStep plus one per value), all stamped with the same timestamp. See Logging metrics and attributes for every batch form.

Send as many UpdateRuns calls as you like; each is validated as a whole and either accepted or rejected with nothing applied.

Closing tells Metrana the writer is done, so the run’s status moves from running to closed:

Terminal window
curl -s -X POST "$METRANA_URL/api/ingest/v1/runs:update" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"sessionId": "1",
"runUpdates": [{
"workspace": "my-workspace",
"project": "api-quickstart",
"runName": "run-001",
"closeRun": {"closeMessage": "finished"}
}]
}'

closeRun may ride on the last data update instead of a separate call; it is processed after everything else in the same update.

Open the project in the portal and the run appears with its attribute and the train/loss chart — or read it back with the Query API.

gRPC takes the same messages in protobuf form. The .proto files live under libs/models/proto in the Metrana source; Python users can install pre-generated classes and stubs with pip install metrana-protobuf (metrana_protobuf.ingestion.v1.svc.api_pb2 and api_pb2_grpc).

With grpcurl, which accepts the JSON form of each message:

Terminal window
HOST=${METRANA_URL#https://}
grpcurl -import-path libs/models/proto -proto ingestion/v1/svc/api.proto \
-H "authorization: Bearer $ACCESS_TOKEN" \
-d '{"run": {"workspace": "my-workspace", "project": "api-quickstart", "runName": "run-002"},
"createProjectIfMissing": true}' \
"$HOST:443" metrana_protobuf.ingestion.v1.svc.IngestService/CreateRun

And in Python:

import grpc
from metrana_protobuf.ingestion.v1.svc import api_pb2, api_pb2_grpc
from metrana_protobuf.ingestion.v1.types import run_definition_pb2, updates_pb2
channel = grpc.secure_channel("app.metrana.ai:443", grpc.ssl_channel_credentials())
stub = api_pb2_grpc.IngestServiceStub(channel)
auth = (("authorization", f"Bearer {access_token}"),)
stub.CreateRun(
api_pb2.CreateRunRequest(
run=run_definition_pb2.Run(workspace="my-workspace", project="api-quickstart", run_name="run-003"),
create_project_if_missing=True,
),
metadata=auth,
)
stub.UpdateRuns(
api_pb2.UpdateRunsRequest(
session_id=1,
run_updates=[
api_pb2.UpdateRunsRequest.RunUpdate(
workspace="my-workspace",
project="api-quickstart",
run_name="run-003",
series=[
updates_pb2.SeriesUpdate(
metric_name="train/loss",
ordering=updates_pb2.ORDERING_MODE_ABSOLUTE_ORDERED,
point_batches=[updates_pb2.PointBatch(first_step=0, values=[2.31, 1.87, 1.52])],
)
],
close_run=updates_pb2.CloseRun(close_message="finished"),
)
],
),
metadata=auth,
)