Loams v0.1.0 is out: run the whole stack yourself with Docker Compose. See the roadmap

API

One port, three protocols, one client object

Loams’s native API is one set of protobuf services served as Connect, gRPC and gRPC-Web on the same port, beside health and reflection. A curl POST of JSON, a grpcurl call and a browser’s gRPC-Web fetch are the same RPC. This page is the reference for that surface: the 53 methods that are registered, the 52 reasons a call can fail with, the REST routes that are still there beside them, and the SDKs.

Reference

The native API, in full.

Scalar, rendered from a protobuf-derived OpenAPI snapshot aligned with current handlers.

A local engine listens on `127.0.0.1:8080`. Every example on this page uses that address. There are no hosted endpoints yet. `LOAMS_AUTH_MODE` picks authentication: `off` (the default, which refuses to start unless every listener is on loopback), `apikey` (`Authorization: Bearer lk_…`), `oidc` (a JWT from a configured issuer), `tailscale`, or a comma list such as `oidc,apikey`. `GetInstance` and the health probes never need a credential.

The reference below is generated from proto/loams/instance/v1/instance.proto,proto/loams/collection/v1/collection.proto,proto/loams/collection/v1/document.proto,proto/loams/collection/v1/query.proto, proto/loams/live/v1/live.proto, proto/loams/errors/v1/errors.proto and the standard gRPC health and reflection contracts. The original schemas use protoc-gen-connect-openapi. Collection/document/Search entries are derived from the current protobuf source and checked against the implemented handlers. It shows the Connect JSON mapping, so every unary call is a POST to /<package>.<Service>/<Method>. Every operation carries a status badge: Available, In progress or Planned.

It loads on request, so none of the reference’s own JavaScript is part of this page’s first load. The description itself is at /openapi/connect.yaml.

Scalar, rendered from the OpenAPI description generated out of the protos. Open it in its own page or read connect.yaml.

Services

What is registered, and what answers.

The difference between the two is the current state of this API.

The API catalogue. Every package that exists, and whether this build serves it. A package listed with available: false is registered with a stub, not left unrouted, so a caller gets a typed refusal instead of a bare 404.
PackageSDK moduleIn this buildNote
loams.instance.v1instanceAvailableServed by every build variant. `GetInstance` answers without a credential; `WhoAmI` reports the authenticated principal, or `dev-loopback` when authentication is off.
loams.collection.v1collectionsdocumentssearchvectorAvailable18 implemented namespace, collection, document and hybrid Search RPCs. They share the REST service layer and carry typed errors, consistency tokens and idempotency on document writes.
loams.live.v1 unstablelivetablesPlannedRegistered with a stub, not left unrouted, so a caller gets `unimplemented` with reason `feature_not_in_variant` and the variant in `metadata` instead of a bare 404. The Live engine, in the default build, is served on its own loopback listener (`127.0.0.1:7710`), with Query, Mutate, Watch and bundle Deploy, not through this main-port stub. Its wire contract may still change, so it is excluded from breaking-change checks.
loams.graph.v1 unstablegraphIn progressLoams Graph: stored property graphs queried with GQL. Served with the `graph` build feature (the `full` variant) while the graph runtime is on; otherwise every RPC answers `feature_not_in_variant`. `RestoreGraph`, `ExportGraph` and `ImportGraph` answer `not_implemented`. Unstable.
loams.house.v1 unstablehouseIn progressLoams House: ClickHouse SQL over Iceberg tables. Not served by this port itself: every `/loams.house.v1.*` path is proxied to the House front named by `--house-endpoint`, and the package is listed as available only while that front is configured and healthy. Without one, every RPC answers `unimplemented` with reason `house_not_configured`. Unstable.
Every RPC registered on the API port: 53 methods across 5 catalogue packages plus the standard gRPC health and reflection services.
RPCShapeStatusWhat it does today
loams.collection.v1.NamespaceService/CreateNamespaceunaryAvailableCreates a namespace through the shared metastore.
loams.collection.v1.CollectionService/CreateCollectionunaryAvailableCreates a collection; replay by name returns the same collection when the schema agrees.
loams.collection.v1.CollectionService/ListCollectionsunary, GET-ableAvailableLists namespace collections with page tokens.
loams.collection.v1.CollectionService/GetCollectionunary, GET-ableAvailableReturns collection schema, manifest, hot tier and backpressure information; accepts aliases.
loams.collection.v1.CollectionService/DropCollectionunaryAvailableDrops a collection through the shared service.
loams.collection.v1.CollectionService/AddFieldsunaryAvailableAdds schema fields with version validation.
loams.collection.v1.CollectionService/ListVersionsunary, GET-ableAvailableLists retained collection manifest versions.
loams.collection.v1.CollectionService/ScanunaryAvailablePins a Lance scan plan for external readers, including its consistency token.
loams.collection.v1.CollectionService/UpdateAliasesunaryAvailableApplies collection alias actions.
loams.collection.v1.CollectionService/SetHotunaryAvailableSets collection hot-tier configuration and returns local status.
loams.collection.v1.CollectionService/WarmCollectionunaryAvailableStarts warming the collection on its owner.
loams.collection.v1.DocumentService/WriteDocumentsunaryAvailableAtomically writes document operations; idempotency keys replay recorded results. Returns token and backlog state.
loams.collection.v1.DocumentService/GetDocumentsunary, GET-ableAvailableReturns requested documents in order with token-aware consistency.
loams.collection.v1.DocumentService/ScrollDocumentsunary, GET-ableAvailablePages documents by primary key with token-aware consistency.
loams.collection.v1.DocumentService/CountDocumentsunary, GET-ableAvailableCounts documents matching a filter.
loams.collection.v1.DocumentService/DeleteByFilterunaryAvailableDeletes matching documents from a pinned snapshot, with explicit partial-write cursors.
loams.collection.v1.DocumentService/PatchByFilterunaryAvailablePatches matching documents from a pinned snapshot; filter writes do not create new documents.
loams.collection.v1.QueryService/Searchunary, GET-ableAvailableHybrid search with typed retrievers, filter IR, fusion, sort, projection, highlights and groups; returns read token and shares the REST search engine.
loams.instance.v1.InstanceService/GetInstanceunary, GET-ableAvailableInstance discovery, alongside the implemented collection and document RPCs. No credentials: an app calls it before sign-in, to learn the instance id, the edition, the API packages and how to sign in.
loams.instance.v1.InstanceService/WhoAmIunary, GET-ableAvailableReports the caller: the OIDC user, the API key or the Tailscale user, with its kind and display name. With authentication off every caller is `dev-loopback`.
loams.live.v1.LiveService/QueryunaryPlannedAnswers `unimplemented` with reason `feature_not_in_variant`. Exposed to the SDK as `loams.tables.query`.
loams.live.v1.LiveService/MutateunaryPlannedSame refusal. Takes an `idempotency_key`, so a retried call returns the recorded commit instead of running again. Exposed as `loams.tables.mutate`.
loams.live.v1.LiveService/DeployunaryPlannedSame refusal. Admin-only, and deliberately not annotated with a facade option, so it stays out of every SDK. Exposed as `loams.tables.deploy`.
loams.live.v1.LiveService/Watchserver streamPlannedSame refusal, on a server stream. Exposed as `loams.live.watch`.
loams.live.v1.LiveService/ModifyQuerySetunaryPlannedSame refusal. Exposed as `loams.live.modifyQuerySet`.
loams.graph.v1.GraphAdminService/GetEngineInfounary, GET-ableAvailableServed with the graph build feature and runtime; otherwise `feature_not_in_variant`.
loams.graph.v1.GraphAdminService/CreateGraphunaryAvailableServed with the graph build feature and runtime; otherwise `feature_not_in_variant`.
loams.graph.v1.GraphAdminService/GetGraphunary, GET-ableAvailableServed with the graph build feature and runtime; otherwise `feature_not_in_variant`.
loams.graph.v1.GraphAdminService/ListGraphsunary, GET-ableAvailableServed with the graph build feature and runtime; otherwise `feature_not_in_variant`.
loams.graph.v1.GraphAdminService/UpdateGraphunaryAvailableServed with the graph build feature and runtime; otherwise `feature_not_in_variant`.
loams.graph.v1.GraphAdminService/DeleteGraphunaryAvailableReturns a `loams.operations.v1.Operation`. Served with the graph build feature and runtime; otherwise `feature_not_in_variant`.
loams.graph.v1.GraphAdminService/GetSchemaunary, GET-ableAvailableServed with the graph build feature and runtime; otherwise `feature_not_in_variant`.
loams.graph.v1.GraphAdminService/RestoreGraphunaryPlannedRegistered; answers `unimplemented` with reason `not_implemented`.
loams.graph.v1.GraphAdminService/ExportGraphunaryPlannedRegistered; answers `unimplemented` with reason `not_implemented`.
loams.graph.v1.GraphAdminService/ImportGraphunaryPlannedRegistered; answers `unimplemented` with reason `not_implemented`.
loams.graph.v1.GraphService/ExecuteunaryAvailableRuns one GQL statement against a graph. Served with the graph build feature and runtime; otherwise `feature_not_in_variant`.
loams.graph.v1.GraphService/ExecuteBatchunaryAvailableServed with the graph build feature and runtime; otherwise `feature_not_in_variant`.
loams.graph.v1.GraphService/ExecuteStreamserver streamAvailableStreams result chunks. Served with the graph build feature and runtime; otherwise `feature_not_in_variant`.
loams.graph.v1.GraphService/ExplainunaryAvailableServed with the graph build feature and runtime; otherwise `feature_not_in_variant`.
loams.house.v1.HouseService/ExecuteQueryserver streamAvailableRuns one ClickHouse statement and streams a header, batches, progress and a summary. A statement that writes needs `read_only = false`. Proxied to the House front; `house_not_configured` without one.
loams.house.v1.HouseService/CancelQueryunaryAvailableCancels a running query by id. Proxied to the House front; `house_not_configured` without one.
loams.house.v1.HouseService/ExplainQueryunary, GET-ableAvailableProxied to the House front; `house_not_configured` without one.
loams.house.v1.HouseService/ListDatabasesunary, GET-ableAvailableLists the caller namespace’s databases (`default` until the lake catalog lands). Proxied to the House front; `house_not_configured` without one.
loams.house.v1.HouseService/ListTablesunary, GET-ableAvailableProxied to the House front; `house_not_configured` without one.
loams.house.v1.HouseService/DescribeTableunary, GET-ableAvailableProxied to the House front; `house_not_configured` without one.
loams.house.v1.HouseService/PreviewTableserver stream, GET-ableAvailableStreams a table’s first rows. Proxied to the House front; `house_not_configured` without one.
loams.house.v1.HouseService/ListQueryHistoryunary, GET-ablePlannedAnswers `not_implemented` until the query log lands.
loams.house.v1.HouseService/GetQueryunary, GET-ablePlannedAnswers `not_implemented` until the query log lands.
loams.house.v1.HouseService/ListPipesunary, GET-ablePlannedAnswers `not_implemented` until pipes land.
loams.house.v1.HouseService/RestoreTableunaryPlannedWill return a `loams.operations.v1.Operation`; answers `not_implemented` today.
grpc.health.v1.Health/CheckunaryAvailableThe standard gRPC health service. `SERVING` for the whole process, and for each service this build serves.
grpc.health.v1.Health/Watchserver streamAvailableThe health stream, over the same envelope as every other server stream here.
grpc.reflection.v1.ServerReflection/ServerReflectionInfoserver streamIn progressServer reflection, mounted in `loams dev` and off elsewhere, so `grpcurl` and Postman can read the contract without a copy of the protos.
Protos that exist but are not in the catalogue. These packages are defined and generated, and no build variant registers them yet, so the engine routes no path for them and GetInstance.services[] does not list them. The reasons above that name one of their methods are already reserved for it. The app mock server covers them for client development.
  • loams.approvals.v1 — ApprovalService: ListApprovals, GetApproval, WatchApprovals, DecideApproval
  • loams.devices.v1 — DeviceService: CreatePairing, ListDevices, RenameDevice, RevokeDevice, RegisterPushTarget, UnregisterPushTarget, GetNotificationPreferences, SetNotificationPreferences, SendTestNotification
  • loams.notifications.v1 — NotificationService: ListNotifications, WatchNotifications, MarkRead
  • loams.postgres.v1 — PostgresService: CreateProject, GetProject, ListProjects, UpdateProject, DeleteProject, CreateBranch, GetBranch, ListBranches, UpdateBranch, DeleteBranch, RestoreBranch, SetDefaultBranch, CreateEndpoint, GetEndpoint, ListEndpoints, UpdateEndpoint, StartEndpoint, SuspendEndpoint, RestartEndpoint, DeleteEndpoint, CreateRole, ListRoles, ResetRolePassword, DeleteRole, CreateDatabase, ListDatabases, DeleteDatabase, GetConnectionInfo, IssueConnectCredential, UpgradeProject, WatchProject
  • loams.operations.v1 — OperationsService: GetOperation, ListOperations, WatchOperations, CancelOperation
Errors

Branch on the reason, not the message.

Every failure a caller has to handle, in one table.

A failed RPC carries a Connect code and one loams.errors.v1.ErrorInfo in its details. The reason in that detail is the stable, registered string; the message is for a person and may change. The SDKs turn a reason into a typed error class so a caller catches a class rather than parsing anything.

52 reasons. The generic ones are the code-to-class mapping every SDK already has; the ones above them are Loams’s own causes.
reasonConnect codeRaised bymetadata
approval_expiredfailed_preconditionDecideApproval—
approval_already_decidedfailed_preconditionDecideApproval—
approval_stale_revisionfailed_preconditionDecideApproval—
requester_cannot_approvepermission_deniedDecideApproval—
decision_proof_invalidpermission_deniedDecideApproval—
step_up_requiredunauthenticatedDecideApproval, CreatePairing—
reason_requiredinvalid_argumentDecideApproval—
invalid_decisioninvalid_argumentDecideApproval—
pairing_expiredfailed_preconditionthe pairing grant—
pairing_usedfailed_preconditionthe pairing grant—
device_revokedunauthenticatedevery RPC from a revoked device—
push_target_unknownnot_foundUnregisterPushTarget—
not_implementedunimplementeda stub handler whose service has not landed yet—
feature_not_in_variantunimplementedany RPC of a catalogue package whose engine is not in this build variantvariant
house_not_configuredunimplementedany loams.house.v1 RPC on a server with no [house] endpoint (--house-endpoint): the main port has no Loams House front to proxy to. A front that is configured but unreachable answers unavailable—
house_syntax_errorinvalid_argumentloams.house.v1 ExecuteQuery, ExplainQuery, PreviewTable: ClickHouse's 62 SYNTAX_ERROR — a statement that does not parse, more than one statement, or a form outside the House's surfaceclickhouse_codeclickhouse_name
house_unknown_tablenot_foundloams.house.v1 statements, DescribeTable, PreviewTable: ClickHouse's 60 UNKNOWN_TABLEclickhouse_codeclickhouse_name
house_unknown_databasenot_foundloams.house.v1 statements and the browsing RPCs: ClickHouse's 81 UNKNOWN_DATABASEclickhouse_codeclickhouse_name
house_access_deniedpermission_deniedloams.house.v1: ClickHouse's 497 ACCESS_DENIED; a read-only user's write (164); CancelQuery of another caller's query; ListQueryHistory with scope NAMESPACE without house:adminclickhouse_codeclickhouse_name
house_read_onlyfailed_preconditionloams.house.v1 ExecuteQuery with read_only absent or true and a statement that writes or changes state: refused before it runs (ClickHouse's 164 READONLY); the desktop asks to confirm and runs it again with read_only = falseclickhouse_codeclickhouse_name
house_timeoutdeadline_exceededloams.house.v1 statements: ClickHouse's 159 TIMEOUT_EXCEEDED — past timeout_ms (or 300 s), or the worker killed 2 s after itclickhouse_codeclickhouse_name
house_memory_limitresource_exhaustedloams.house.v1 statements: ClickHouse's 241 MEMORY_LIMIT_EXCEEDED, or the worker killed over its memoryclickhouse_codeclickhouse_name
house_too_many_queriesresource_exhaustedloams.house.v1 statements: ClickHouse's 202 TOO_MANY_SIMULTANEOUS_QUERIES — no worker free within the acquire timeoutclickhouse_codeclickhouse_name
house_quota_exceededresource_exhaustedloams.house.v1 statements past a namespace quotaquotalimit
house_disabled_functionpermission_deniedloams.house.v1 statements: ClickHouse's 344 SUPPORT_IS_DISABLED — a table function, function, engine, statement or system table the House's deny list refusesclickhouse_codeclickhouse_name
house_cancelledcanceledloams.house.v1 ExecuteQuery and PreviewTable: ClickHouse's 394 QUERY_WAS_CANCELLED — CancelQuery, or the statement's worker stopped for itclickhouse_codeclickhouse_name
house_query_not_foundnot_foundloams.house.v1 CancelQuery (no running query of that id) and GetQuery (no logged query of the caller's with that id)query_id
house_query_failedunknownloams.house.v1 statements: any other error the engine raised whose ClickHouse HTTP status is not 4xx (one whose status is 4xx answers invalid_argument), with its ClickHouse code and nameclickhouse_codeclickhouse_name
project_not_foundnot_foundevery loams.postgres.v1 RPC that names a projectproject
branch_has_childrenfailed_preconditionDeleteBranchchildren
branch_protectedfailed_preconditionDeleteBranch, RestoreBranch (an agent's goes through loams.approvals.v1 instead)branch
lsn_out_of_retentionfailed_preconditionCreateBranch, RestoreBrancholdest_lsnhistory_retention
endpoint_exists_for_branchalready_existsCreateEndpoint (a second read-write endpoint)endpoint
compute_start_failedunavailableStartEndpoint, RestartEndpoint (the Operation's error)compute
quota_exceededresource_exhaustedany loams.postgres.v1 create or resize past a limitquotalimit
storage_unavailableunavailableany loams.postgres.v1 RPC that needs the pageserver, loams-wal or the storage controller while it is downcomponent
secret_already_issuedfailed_preconditionCreateRole, ResetRolePassword, IssueConnectCredential replayed with the same idempotency_key: the secret is answered once, and the idempotency ledger keeps only that it was issued, never the secretrole
live_bad_cursorinvalid_argumentloams.live.v1 Query, Mutate and the session's queries: a function's paginate got a cursor that is malformed, altered, or from another app, table or index. Named by the Live engine; reaches the wire once Live moves onto the main Connect port—
live_worker_crashedinternalloams.live.v1 Query, Mutate and the session's queries: the isolated worker process running the function crashed or was killed; the call's transaction never committed, and a mutation is not retried. Live's own wire code is ERROR_CODE_FUNCTION_ERROR; internal is provisional until Live errors are mapped to Connect codes. Named by the Live engine; reaches the wire once Live moves onto the main Connect port—
live_deploy_conflictfailed_preconditionloams.live.v1 Deploy: the deploy's expected_active is not the app's active deployment, because another deploy activated in between; the message names both. The deployment's record is failed. Named by the Live engine; reaches the wire once Live moves onto the main Connect port—
invalid_argumentinvalid_argumentany RPC: a malformed field, an unparseable valuefield
not_foundnot_foundany RPC: the named resource does not existkindname
already_existsalready_existsany RPC that creates a named resource—
permission_deniedpermission_deniedany RPC the caller's role may not make—
token_expiredunauthenticateda rejected access token—
unauthenticatedunauthenticatedany RPC with no or an unusable credential—
failed_preconditionfailed_preconditionany RPC whose preconditions do not hold—
resource_exhaustedresource_exhausteda backpressure or quota refusalretry_after_ms
unavailableunavailablea dependency is down or this node cannot serve the read—
deadline_exceededdeadline_exceededthe caller's deadline passed—
abortedaborteda concurrent write won; the caller retries—
internalinternala bug; the message and the request id go to the log—
How to branch.
  • Branch on `ErrorInfo.reason`. It is `snake_case`, unique, and registered; within an API major version a reason is never renamed and never removed.
  • Never branch on the Connect code alone. Several reasons share one code, and a new reason can be added under a code you already handle.
  • Never branch on `message`. It is written for a person, in the caller’s locale, and it may change.
  • Treat a `reason` you do not know as the code says, not as a bug: the registry only ever grows, and a newer server may raise a reason your SDK has never heard of.
The typed errors an SDK hands you. Each generic reason becomes the error class its Connect code names, so not_found is a NotFoundError in TypeScript. Two reasons get their own class because they are worth catching on their own: feature_not_in_variant becomes a FeatureNotInVariantError carrying .variant, and token_expired a TokenExpiredError, which the runtime reaches only after its one automatic refresh and retry have already failed. Everything else falls through to the base LoamsError.

Stable Within an API major version a reason is never renamed and never removed. A bug fix is a new reason; a deprecated one keeps working and stops being returned. The TypeScript SDK compiles this same list into a TypeScript union, so a switch over reason there is checked against every reason that existed when the SDK was generated.

REST

The REST routes that still exist.

Each one has an RPC it becomes. None of them has moved yet.

These routes still answer exactly as they did, on the same port as the Connect API beside them. Resource names are in the path rather than in a request message, which is the one thing that changes when a route becomes an RPC.

The 29 application routes under /v1, each with the RPC that replaces it. The replacement is not the same as the route being gone: a route is removed when its RPC answers, so both are live at once today.
MethodRouteBecomes
POST/v1/namespacesloams.collection.v1.NamespaceService/CreateNamespace
POST/v1/namespaces/{ns}/streamsloams.stream.v1.StreamService/CreateStream
GET/v1/namespaces/{ns}/streamsloams.stream.v1.StreamService/ListStreams
GET/v1/namespaces/{ns}/streams/{stream}loams.stream.v1.StreamService/DescribeStream
POST/v1/namespaces/{ns}/streams/{stream}/partitions/{partition}/recordsloams.stream.v1.StreamService/Produce
GET/v1/namespaces/{ns}/streams/{stream}/partitions/{partition}/recordsloams.stream.v1.StreamService/Fetch
POST/v1/namespaces/{ns}/streams/{stream}/eventsloams.stream.v1.StreamService/ProduceCloudEvents
GET/v1/namespaces/{ns}/streams/{stream}/partitions/{partition}/eventsloams.stream.v1.StreamService/FetchCloudEvents
POST/v1/namespaces/{ns}/linksloams.link.v1.LinkService/CreateLink
GET/v1/namespaces/{ns}/linksloams.link.v1.LinkService/ListLinks
GET/v1/namespaces/{ns}/links/{link}loams.link.v1.LinkService/DescribeLink
POST/v1/namespaces/{ns}/collectionsloams.collection.v1.CollectionService/CreateCollection
GET/v1/namespaces/{ns}/collectionsloams.collection.v1.CollectionService/ListCollections
GET/v1/namespaces/{ns}/collections/{c}loams.collection.v1.CollectionService/GetCollection
DELETE/v1/namespaces/{ns}/collections/{c}loams.collection.v1.CollectionService/DropCollection
POST/v1/namespaces/{ns}/collections/{c}/fieldsloams.collection.v1.CollectionService/AddFields
GET/v1/namespaces/{ns}/collections/{c}/versionsloams.collection.v1.CollectionService/ListVersions
POST/v1/namespaces/{ns}/collections/{c}/scanloams.collection.v1.CollectionService/Scan
POST/v1/namespaces/{ns}/aliasesloams.collection.v1.CollectionService/UpdateAliases
POST/v1/namespaces/{ns}/collections/{c}/documentsloams.collection.v1.DocumentService/WriteDocuments
POST/v1/namespaces/{ns}/collections/{c}/documents/getloams.collection.v1.DocumentService/GetDocuments
POST/v1/namespaces/{ns}/collections/{c}/documents/scrollloams.collection.v1.DocumentService/ScrollDocuments
POST/v1/namespaces/{ns}/collections/{c}/documents/countloams.collection.v1.DocumentService/CountDocuments
POST/v1/namespaces/{ns}/collections/{c}/documents/delete_by_filterloams.collection.v1.DocumentService/DeleteByFilter
POST/v1/namespaces/{ns}/collections/{c}/documents/patch_by_filterloams.collection.v1.DocumentService/PatchByFilter
PUT/v1/namespaces/{ns}/collections/{c}/hotloams.collection.v1.CollectionService/SetHot
POST/v1/namespaces/{ns}/collections/{c}/warmloams.collection.v1.CollectionService/WarmCollection
POST/v1/namespaces/{ns}/queryloams.collection.v1.QueryService/Search
POST/v1/namespaces/{ns}/sqlloams.sql.v1.SqlService/Query
Kept as HTTP. These are probes and standards-fixed endpoints, not an application API.
MethodRouteBecomes
GET/healthkept; also grpc.health.v1.Health/Check
GET/readykept; also grpc.health.v1.Health/Check
Node-to-node. These move to a separate internal package on the cluster listener and are never in an SDK.
MethodRouteBecomes
POST/internal/v1/reads/{op}loams.internal.v1.ForwardService/Read
GET/internal/v1/node/statsloams.internal.v1.NodeService/GetNodeStats
POST/internal/v1/hot/statusloams.internal.v1.HotService/GetHotStatus
POST/internal/v1/hot/warmloams.internal.v1.HotService/Warm
Not on this page.
  • The console contract (`/api/v1/*`): 47 method and path pairs that exist as an OpenAPI contract served by a mock, not as routes on the engine.
  • The metastore’s Raft transport (`/internal/v1/raft/*`, `/internal/v1/meta/*`): node-to-node, on the cluster listener.
  • The compatibility surfaces, which keep their own ports and their own wire formats: Qdrant, Elasticsearch, Flight SQL, the Postgres and MySQL wire protocols, MCP and Resonate.
  • The planned `loams.postgres.v1` console routes (`/v1/namespaces/{ns}/postgres/projects/…`): no server serves them; `loams pg-control` serves the RPCs only.
  • `/loams.house.v1.*`: not a route of this server but a proxy to the House front named by `--house-endpoint`.
SDKs

Thirteen languages. One runtime contract.

Implemented clients share required conformance fixtures; service coverage and publication have separate milestones.

13 SDK runtimes; 13 implemented with verified conformance commands. Registry names are distribution targets, not publication claims. Waves record the original implementation order.
LanguagePackageProtocolDistribution targetWaveStatus
TypeScript / JavaScript@loams/clientConnectnpm1Implemented
PythonloamsConnectPyPI1Implemented
Goloams.dev/goConnectGo module proxy1Implemented
RustloamsConnectcrates.io1Implemented
SwiftLoamsConnectSwiftPM2Implemented
Kotlindev.loams:loams-kotlinConnectMaven Central2Implemented
Javadev.loams:loamsgRPCMaven Central2Implemented
C# / .NETLoamsgRPCNuGet2Implemented
Dart / FlutterloamsConnectpub.dev3Implemented
RubyloamsgRPC / ConnectRubyGems3Implemented
PHPloams/loamsgRPC / ConnectPackagist3Implemented
C++loamsgRPCvcpkg overlay, Conan3Implemented
Objective-CLoamsgRPCSwiftPM target only3Implemented
One runtime contract. The language implementations share required fixtures for idempotency, tokens, pagination and typed errors. Ruby and PHP support a Connect-unary fallback where native gRPC is unavailable; streaming depends on the transport. Runtime conformance does not imply that every future service has an implemented server.

TypeScript and JavaScript

@loams/client is the reference the other twelve follow. It is one Loams object with namespaced modules, generated from the proto annotations, and it works in a browser, in Node, in Deno and in Bun because one port serves Connect, gRPC and gRPC-Web. The default transport is Connect over fetch.

The quickstartOne client object with namespaced modules, generated from the proto annotations. No module name and no RPC path is written by hand anywhere.
import { Loams, apiKey, FeatureNotInVariantError } from '@loams/client';

const loams = new Loams({
  endpoint: 'http://127.0.0.1:8080',
  auth: apiKey(process.env.LOAMS_API_KEY!),   // or envToken(), oidcExchange({…})
});

const info = await loams.instance.getInstance({});   // no auth needed
await loams.system.guard('live');                    // costs no RPC once cached

try {
  await loams.tables.mutate(request);
} catch (error) {
  if (error instanceof FeatureNotInVariantError) {
    // This build variant does not carry loams.live.v1.
    console.warn(error.variant);                    // 'standard'
  }
}
Feature-detect instead of probingGetInstance.services[] says which packages this binary serves. It costs one call, needs no credential, and is cached, so twenty availability checks make one request.
const { served, unavailable } = await loams.system.catalogue();

if (await loams.system.available('live')) {
  // loams.live.v1 is in this build.
}

// Guarded, the same check that throws FeatureNotInVariantError the server's
// own refusal maps to, so one catch covers both paths:
try {
  await loams.system.guard('live');
} catch (error) {
  if (error instanceof FeatureNotInVariantError) {
    console.warn(`${error.reason}: ${error.variant}`);
  }
}

// The SDK's packages against the server's, as a compatibility report:
const report = await loams.system.version();
console.log(report.protoRev, report.serverVersion, report.missing);
Branch on the reasonreason is the stable string, code is the transport-level class, and message is for a person. Only reason is safe to switch on.
import {
  LoamsError,
  FeatureNotInVariantError,
  TokenExpiredError,
  isLoamsError,
} from '@loams/client';

try {
  await loams.instance.getInstance({});
} catch (error) {
  if (error instanceof FeatureNotInVariantError) {
    // reason: 'feature_not_in_variant', metadata.variant: 'standard'
  } else if (error instanceof TokenExpiredError) {
    // The SDK has already refreshed once and retried; this is the second failure.
  } else if (isLoamsError(error)) {
    const { code, reason, metadata, hint } = error as LoamsError;
    // A reason this SDK version does not know is still a reason: the registry
    // only grows, so fall through to the code.
  }
}
Transports: one port, three protocolsThe default is Connect over fetch, which is the browser, Node, Deno and Bun path. A server can use HTTP/2 gRPC; a browser, which cannot speak HTTP/2, uses gRPC-Web on the same port.
// Node, Deno, Bun and browsers: Connect over fetch (the default).
const loams = new Loams({ endpoint });

// Node only: HTTP/2 gRPC, from the /node subpath so a bundler never follows
// the Node transport into a browser build it cannot satisfy.
import { createNodeTransport } from '@loams/client/node';
const overHttp2 = new Loams({ endpoint, transport: createNodeTransport() });

// Browsers: gRPC-Web. Same client code, same typed answers.
import { createGrpcWebTransport } from '@connectrpc/connect-web';
const inBrowser = new Loams({
  endpoint,
  transport: createGrpcWebTransport({ baseUrl: endpoint }),
});
Consistency tokens across callsOff by default. On, the client merges the tokens a write returns and attaches the merged token to later reads, which is read-your-writes without threading a token through every call.
const loams = new Loams({ endpoint, auth: apiKey(key), sessionConsistency: true });

await loams.tables.mutate(request);   // its ConsistencyToken is recorded
const rows = await loams.tables.query(request);   // carries the merged token

// Or per call, without the session:
await loams.tables.query(request, { consistency: 'EVENTUAL' });
The generated modules today: three, from two services. A module is a facade name, so loams.live and loams.tables are two views of one service.
ModuleServiceCalls
loams.instanceloams.instance.v1.InstanceServicegetInstancewhoAmI
loams.liveloams.live.v1.LiveServicewatchmodifyQuerySet
loams.tablesloams.live.v1.LiveServicequerymutatedeploy

Generated. The messages and service descriptors, and the facade: one typed interface per annotated service, one method per facade call, the binding table the runtime dispatches through, and the Reason union read from the registry. Regenerated by `buf generate` and never hand-edited.

Hand-written, once. The runtime behind those methods: transport, token sources, retry and backoff, the error mapping, the consistency-token store, the pagination iterator and the streams. It contains no method names and no RPC paths.

So adding an SDK method is annotating a service with a module and a method with a facade call, then regenerating — thirteen languages cannot drift, because there is one generator. A service with no module annotation is in no SDK, which is how an admin-only RPC stays out of every one of them.

Without an SDK: curl

Unary RPCs are plain HTTP over the Connect protocol, so no client library is needed to try one. The bodies below are the recorded conformance fixtures, captured from a real local engine.

Run typed hybrid SearchAfter creating the demo/kb collection in the quickstart, combine vector and text retrievers through the implemented QueryService. This calls the same search engine as native REST.
curl -s http://127.0.0.1:8080/loams.collection.v1.QueryService/Search \
  -H 'content-type: application/json' \
  -d '{
    "namespace": "demo",
    "collection": "kb",
    "retrievers": [
      {"vector": {"field": "embedding", "query": [1, 0, 0], "k": 10}},
      {"text": {"query": {"match": {"field": "body", "text": "refund"}}, "k": 10}}
    ],
    "fusion": {"rrf": {"k": 60}},
    "limit": 3
  }'
Ask the instance what it isDiscover the instance before selecting a service. A read marked NO_SIDE_EFFECTS can also be a cacheable GET. GetInstance never needs a credential, whatever LOAMS_AUTH_MODE is.
curl -s http://127.0.0.1:8080/loams.instance.v1.InstanceService/GetInstance \
  -H 'content-type: application/json' \
  -d '{}'

# The same call as a GET, which a cache may store:
curl -s -G http://127.0.0.1:8080/loams.instance.v1.InstanceService/GetInstance \
  --data-urlencode 'encoding=json' --data-urlencode 'message={}'
Ask who you areWhoAmI reports the principal the request authenticated as. With LOAMS_AUTH_MODE off (the default, loopback only) every caller is dev-loopback; with apikey or oidc, send Authorization: Bearer and the answer names that key or user.
curl -s http://127.0.0.1:8080/loams.instance.v1.InstanceService/WhoAmI \
  -H 'content-type: application/json' \
  -d '{}'

# With authentication off:
# { "principal": { "id": "dev-loopback", "displayName": "Local developer" } }

# With LOAMS_AUTH_MODE=apikey, the key is read from the environment:
curl -s http://127.0.0.1:8080/loams.instance.v1.InstanceService/WhoAmI \
  -H 'content-type: application/json' \
  -H "authorization: Bearer $LOAMS_API_KEY" \
  -d '{}'
Call a package this build does not carryThe reason feature_not_in_variant and metadata.variant say "not in this build", which is different from not_implemented, which says "not written yet".
curl -s http://127.0.0.1:8080/loams.live.v1.LiveService/Query \
  -H 'content-type: application/json' \
  -d '{}'

# 501, and the ErrorInfo decodes to:
#   reason: "feature_not_in_variant"
#   metadata: { "variant": "standard" }
Health and reflectionThe standard gRPC services, so grpcurl, Postman and a load balancer all work against the same port.
# Health, over the Connect JSON mapping:
curl -s http://127.0.0.1:8080/grpc.health.v1.Health/Check \
  -H 'content-type: application/json' \
  -d '{"service":"loams.instance.v1.InstanceService"}'
# { "status": "SERVING" }

# Reflection, over real gRPC. Mounted in `loams dev`, off elsewhere:
grpcurl -plaintext 127.0.0.1:8080 list
grpcurl -plaintext 127.0.0.1:8080 describe loams.instance.v1.InstanceService
Playground

Run the Rust sample.

Compiled to WebAssembly, fetched only when you press Run.

The sample below is Rust, and it does the one job this page can honestly show running: it reads sample ErrorInfo records and classifies each one against the registry — the same 52 rows printed above, the same ones the TypeScript SDK compiles its Reason union from. Press Run and the result, the per-record verdict and the raw JSON report appear beside the sample data.

Where the compilation happens. Not in your browser. The Rust is compiled to wasm32-unknown-unknown by the repository’s build script, scripts/build-playground-wasm.sh, and the resulting 56 KB module is checked in and served from /wasm/loam-reasons.wasm. There is no Rust toolchain that runs in a browser, so compiling on the click is not something this page can honestly offer; what the click does is fetch the module, instantiate it and execute the compiled program on your sample data. The source shown in the panel is the exact file that was compiled — the build script installs it from the same path.

The sandbox boundary. The module runs inside your browser’s own WebAssembly runtime, which is the sandbox: it has no network, no filesystem, no environment variables and no access to this site’s origin beyond the one fetch of its own bytes. It is instantiated with an empty import object, so it cannot call out to anything at all. There is no server-side execution path here, no code from this page or from you is sent anywhere, and no credential is in the module or in the sample data. What it reads is the registry compiled into it, and what you type.

What can go wrong, and what you see. A fetch or instantiation failure says so and names the stage. A Rust panic! traps the module, and the message the Rust side recorded is shown rather than a blank panel — press Run the panic sample to see that path for real rather than take our word for it. A trap leaves the WebAssembly instance usable, so a panic does not poison the next run.

Sample dataJSON, one ErrorInfo per object. Edit it.
Result

Nothing has run yet. Press Run and this panel shows what the compiled Rust program made of the sample data: the registry’s own invariants, then each record classified into its Connect code and its error class.