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.
1. Authenticate
Section titled “1. Authenticate”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):
export METRANA_API_KEY="…" # from the portalexport 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)2. Create the run
Section titled “2. Create the run”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.
3. Log attributes and a series
Section titled “3. Log attributes and a series”UpdateRuns addresses the run by name and carries any mix of attributes and series:
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:
sessionIdidentifies 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/tagsis one of only two writablesys/attributes (the other issys/description). - The series is identified by
metricNameplus itsscaleandlabels, both omitted here (the default scale,ML_STEP, and no labels). - The batch holds five points at steps
0–4(firstStepplus 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.
4. Close the run
Section titled “4. Close the run”Closing tells Metrana the writer is done, so the run’s status moves from running to closed:
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.
The same calls over gRPC
Section titled “The same calls over gRPC”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:
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/CreateRunAnd in Python:
import grpcfrom metrana_protobuf.ingestion.v1.svc import api_pb2, api_pb2_grpcfrom 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,)Next steps
Section titled “Next steps”- Creating runs — experiments, directories, forking and name conflicts.
- Logging metrics and attributes — series identity, batching, histograms and scatter plots.
- Streaming ingestion — the high-throughput path the SDK uses.