Transports and encoding
The Ingestion and Query APIs are defined in protobuf and served two ways from the same host. This page applies to both.
| Ingestion | Query | |
|---|---|---|
| Target | app.metrana.ai:443 (TLS) |
app.metrana.ai:443 (TLS) |
| Service | metrana_protobuf.ingestion.v1.svc.IngestService |
metrana_protobuf.query.v1.svc.QueryService |
| Definition | ingestion/v1/svc/api.proto |
query/v1/svc/api.proto |
Self-hosted deployments use their own host (the url inside your API token). The .proto files are under
libs/models/proto in the Metrana source; import paths are relative to that directory (for example
common/v1/series.proto). Python users can pip install metrana-protobuf for generated messages and stubs.
- Authentication: the
authorizationmetadata key,Bearer <access token>. See Authentication. - Message size: the servers accept requests up to 4 MiB. Query responses can be much larger than 4 MiB — raise your client’s maximum receive size (the SDK uses 128 MiB), or request smaller pages.
- Compression: the Ingestion API accepts zstd-compressed requests.
- Health: both servers implement the standard
grpc.health.v1.Healthservice, without authentication. - gRPC-Web: the Query API also accepts gRPC-Web, for browser clients.
- Reflection is not enabled: give tools such as
grpcurlthe.protofiles.
Each unary RPC is also served as POST with a JSON body:
| API | Base path | Example |
|---|---|---|
| Ingestion | https://app.metrana.ai/api/ingest/v1 |
POST /api/ingest/v1/runs:update |
| Query | https://app.metrana.ai/api/query/v1 |
POST /api/query/v1/runs:list |
The body is the RPC’s request message in JSON, and a success response (200) is the response message in JSON. Each
RPC’s route is listed on its reference entry. Streaming RPCs (InitializeRunLogging, UpdateRunStream) have no
REST route. Send Content-Type: application/json and the Authorization: Bearer … header.
JSON encoding
Section titled “JSON encoding”REST bodies use the standard protobuf JSON mapping:
| Protobuf | JSON | Example |
|---|---|---|
| Field names | lowerCamelCase in responses; requests accept lowerCamelCase or the original snake_case | runName (or run_name) |
int64, uint64 |
string (requests also accept numbers) | "firstStep": "1000" |
int32, uint32 |
number | "ordinal": 0 |
double, float |
number; see non-finite values | "value": 0.25 |
bool |
true / false |
|
bytes |
standard base64 string | "runId": "AAAAAAAAAAAAAAAAAAAAAQ==" |
| enum | value name as a string (requests also accept the number) | "ordering": "ORDERING_MODE_ABSOLUTE_ORDERED" |
google.protobuf.Timestamp |
RFC 3339 string | "creationTime": "2026-09-30T09:00:00Z" |
map<K, V> |
object | "labels": {"gpu": "0"} |
repeated |
array | "values": [0.41, 0.40] |
| message | object | "run": {…} |
| oneof | the chosen member’s field, alone | {"path": "lr", "floatValue": 0.001} |
Responses omit fields that hold their default value: 0, false, "", empty arrays and maps, and unset
messages. A missing field means “default”, not “absent from the schema”. Fields declared optional are the
exception: they appear whenever set, even to a default value, so their absence means “not set”.
Unknown fields in a request are rejected.
Non-finite values
Section titled “Non-finite values”Requests may send NaN, Infinity and -Infinity as the strings "NaN", "Infinity" and "-Infinity".
Responses write a non-finite double as null. Where telling them apart matters — NaN points in float series, for
example — use gRPC, or the fields the Query API provides for it (such as bucketsWithNans on bucketed series).
Choosing a transport
Section titled “Choosing a transport”- REST needs nothing beyond an HTTP client, works in any language, and is easy to debug with
curl. - gRPC gives generated, typed clients, smaller messages, compression and HTTP/2 multiplexing — and is the only way to use the streaming ingestion path.
Both run the same validation and return the same data.