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.
Endpoint
Section titled “Endpoint”- Sandbox:
sandbox.evidentsource.com:50051, over TLS. - Your deployment: port
50051on 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.
| Purpose | RPCs |
|---|---|
| Databases | CreateDatabase, DeleteDatabase, FetchCatalog (streams) |
| A database’s position | FetchLatestDatabase, FetchCachedDatabase, AwaitDatabase, DatabaseEffectiveAtTimestamp |
| Writes | Transact, ExecuteStateChange, and the fire-and-forget TransactAsync, ExecuteStateChangeAsync |
| Events | QueryEvents (streams), EventById, FetchEventsByRevisions, ScanIndexKeys (streams) |
| The log | ScanDatabaseLog (streams), FetchTransactionById |
| State Views | ListStateViewDefinitions (streams), FetchStateViewAtRevision, ExpireStateViewSnapshots |
| State Changes | ListStateChanges (streams) |
| Extract checkpoints | CreateExtractCheckpoint, ReleaseExtractCheckpoint, ListExtractCheckpoints |
| Updates | SubscribeDatabaseUpdates (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 name a revision
Section titled “Event and state reads name a revision”Event and state reads run at a revision, not at a timestamp. Definition listings (ListStateChanges and ListStateViewDefinitions) take a database name without a revision.
QueryEventstakes a revision, and optionally an effective-time range. That range names its cut,end_atorend_at_now; one with neither, or both, isINVALID_ARGUMENT. The response metadatax-effective-time-end-atreports the cut applied.FetchStateViewAtRevisiontakes a revision, and for an effective-time-ordered view its cut:effective_time_end_at, oreffective_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 stillINVALID_ARGUMENT.FetchStateViewAtRevisionpresents a State View only on request. Withacceptset, a view whose definition names a presenter is answered by it, as an HTTP read is:acceptis negotiated among the presenter’s offers, the presenter receives the read’s bindings as{attribute}.{parameter}, as from an HTTP read, thenpresentation_parametersin order, and the reply’spresentationcarries the body, its content type and the continuation, with the view’s metadata but not its state. Withoutaccept, 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 ignoresaccept. Presentation parameters withoutaccept, or for a view that names no presenter, areINVALID_ARGUMENT; a type the presenter doesn’t offer isFAILED_PRECONDITION, with error kindnot-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.
Writes
Section titled “Writes”Transactappends events with append conditions. A transaction carries atransaction_id; resending the same id returns the recorded result instead of committing twice, so a retry is safe.ExecuteStateChangeruns a State Change at a revision and commits what itsdecidereturns.- A violated append condition fails with
FAILED_PRECONDITION.
Expiring a State View’s snapshots
Section titled “Expiring a State View’s snapshots”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.
Authentication
Section titled “Authentication”Send a bearer token in the authorization metadata. See Security.
The SDKs wrap this service in language-native types. See SDKs.