Skip to content

gRPC API

EvidentSource serves one gRPC service, EvidentSource, defined in evidentsource/interface/service.proto. The SDKs are built on it. The HTTP API carries the same model over JSON; see REST API.

  • Sandbox: sandbox.evidentsource.com:50051, over TLS.
  • Your deployment: port 50051 on each server, or whatever your load balancer exposes.

Seven RPCs stream their replies; the rest are unary.

When authentication is required, streams require credentials with an expiry. A credential’s expiry ends the stream even when authentication is optional: at expiry the server stops producing messages, including while a subscription is idle. A client that has stopped reading gets the terminal status once it reads again, and messages already handed to the transport before expiry can still arrive. With authentication optional, the stream guard imposes no additional missing-expiry requirement; configured credential verification can still require one. Credentials with no expiry, such as identities taken from ALB headers without verification or an unverified bearer token without exp, don’t bound a stream, and a stream opened with one is refused when authentication is required. Reconnect with valid credentials to continue.

PurposeRPCs
DatabasesCreateDatabase, DeleteDatabase, FetchCatalog (streams)
A database’s positionFetchLatestDatabase, FetchCachedDatabase, AwaitDatabase, DatabaseEffectiveAtTimestamp
WritesTransact, ExecuteStateChange, and the fire-and-forget TransactAsync, ExecuteStateChangeAsync
EventsQueryEvents (streams), EventById, FetchEventsByRevisions, ScanIndexKeys (streams)
The logScanDatabaseLog (streams), FetchTransactionById
State ViewsListStateViewDefinitions (streams), FetchStateViewAtRevision, ExpireStateViewSnapshots
State ChangesListStateChanges (streams)
Extract checkpointsCreateExtractCheckpoint, ReleaseExtractCheckpoint, ListExtractCheckpoints
UpdatesSubscribeDatabaseUpdates (streams)

SubscribeDatabaseUpdates streams a database’s position as it advances. It carries no events and takes no filters: read the events you want at the revision it reports.

A subscription follows the database it opened on. If that database is deleted and the name created again, the replacement’s transactions are not reported on the old stream; subscribe again to follow the new one.

Database identity and optional deletion key

Section titled “Database identity and optional deletion key”

Every request continues to work with a database name alone. Database responses also expose database_id, a UUIDv7 identifying the instance, and created_at derived from its embedded timestamp at millisecond precision. Create, read, and subscription responses retain that ID; recreation changes it.

DeleteDatabaseRequest.database_id is optional. Supply a previously observed ID when you want safe deletion retries: the request deletes only that instance. If the name no longer points to the supplied ID, it succeeds without touching a replacement. Retry with the same captured ID.

The reply always names the instance the delete pinned itself to, in name and database_id. Its database field — the deleted instance’s final state — is present only when the node that ran the delete had that instance open. In a cluster that is the name’s owner, which need not be the node you asked, so whether you get a database back does not depend on which node you reached. It is absent when the delete matched no current instance, and when the owner never opened the database: neither case has a revision anyone knows, and a zero there would read as a database that ended empty. An absent database is not a failure — the delete succeeded either way.

Without the ID, deletion targets the name’s current instance and returns NOT_FOUND if absent. A supplied empty, malformed, or non-v7 UUID returns INVALID_ARGUMENT. IDs are identity tokens, never ordering keys. Kafka topics are retained for operator cleanup.

Event and state reads run at a revision, not at a timestamp. Definition listings (ListStateChanges and ListStateViewDefinitions) take a database name without a revision.

  • QueryEvents takes a revision, and optionally an effective-time range. That range names its cut, end_at or end_at_now; one with neither, or both, is INVALID_ARGUMENT. The response metadata x-effective-time-end-at reports the cut applied.
  • FetchStateViewAtRevision takes a revision, and for an effective-time-ordered view its cut: effective_time_end_at, or effective_time_end_at_now. The reply reports the cut applied. A revision-ordered view ignores a well-formed cut, and its reply reports none; both fields together, or a malformed time, are still INVALID_ARGUMENT.
  • FetchStateViewAtRevision presents a State View only on request. With accept set, a view whose definition names a presenter is answered by it, as an HTTP read is: accept is negotiated among the presenter’s offers, the presenter receives the read’s bindings as {attribute}.{parameter}, as from an HTTP read, then presentation_parameters in order, and the reply’s presentation carries the body, its content type and the continuation, with the view’s metadata but not its state. Without accept, or with a blank one, the reply is the state. The presenter receives only the bindings the view’s selector declares; an HTTP read passes every query parameter, so there an undeclared dotted one reaches it too. A view that names no presenter ignores accept. Presentation parameters without accept, or for a view that names no presenter, are INVALID_ARGUMENT; a type the presenter doesn’t offer is FAILED_PRECONDITION, with error kind not-acceptable.
  • To turn a wall-clock time into a revision, call DatabaseEffectiveAtTimestamp. It returns the latest revision whose transaction was recorded at or before that time.

There are no as_of_transaction_time or as_of_effective_time parameters. See Bi-temporal reads for how the two steps combine.

  • Transact appends events with append conditions. A transaction carries a transaction_id; resending the same id returns the recorded result instead of committing twice, so a retry is safe.
  • ExecuteStateChange runs a State Change at a revision and commits what its decide returns.
  • A violated append condition fails with FAILED_PRECONDITION.

ExpireStateViewSnapshots makes a State View’s next read fold from its first event, for every version of the view. Use it when a stored snapshot cannot be read, or holds a wrong fold, or to force a full refold. It deletes nothing: reads and writes move to a fresh set of snapshots, and the old ones are deleted by the cold-snapshot sweep once nothing has written to them for state_view_snapshot_series_ttl_secs (7 days by default). Other views, and the same view in other databases, keep theirs. The server you call through may take up to its replication lag plus a second to move; until then it reads the old snapshots.

The reply is the view’s new epoch, 1 after the first call. A call carries no idempotency key, so retrying one whose outcome was unknown may expire twice; that costs one more refold and nothing else.

It needs the deployer role on the database, checked before anything else, and a call with no identity is refused. A view never published in the database is NOT_FOUND.

Send a bearer token in the authorization metadata. See Security.

The SDKs wrap this service in language-native types. See SDKs.