Skip to main content

gRPC API Reference

Laredo exposes three gRPC services. OAM and Query share a single port (default 4001). The Replication service runs on a separate port per fan-out target (default 4002).

All services use Connect (compatible with gRPC, gRPC-Web, and Connect protocols).

OAM Service (laredo.v1.TableSyncOAM)

Port: 4001 (default, configurable via grpc.port)

Status & Monitoring

RPCDescription
GetStatusOverall service state, source statuses, pipeline statuses
GetTableStatusPipelines and indexes for a specific table
GetPipelineStatusSingle pipeline status with indexes
WatchStatusServer-streaming status events (state changes, row changes)
CheckReadyReadiness check (global, per-source, per-table, per-pipeline)

WatchStatus

Server-streaming RPC that pushes engine events to the client in real time. The stream remains open until the client disconnects.

rpc WatchStatus(WatchStatusRequest) returns (stream WatchStatusResponse);

Request fields:

FieldTypeDescription
tablesrepeated stringFilter to events for these tables (format schema.table). Empty means all tables.
pipeline_idsrepeated stringFilter to events for these pipeline IDs. Empty means all pipelines.

Both filters are applied independently. An event matches if it passes both filters (or if the filter is empty). Service-level and source-level events are always delivered regardless of filters.

Stream event types:

Each WatchStatusResponse contains a timestamp and exactly one of the following event variants:

EventFieldsDescription
service_state_changeold_state, new_state (ServiceState enum)The overall service state changed (e.g., BASELINING to STREAMING).
pipeline_state_changepipeline_id, old_state, new_state (PipelineState enum)A pipeline transitioned between states (e.g., INITIALIZING to BASELINING).
source_state_changesource_id, event_type, messageA source connected or disconnected. event_type is "connected" or "disconnected".
row_changepipeline_id, schema, table, action, position, xidA change was applied to a target. action is INSERT, UPDATE, DELETE, or TRUNCATE.

Backpressure: Each watcher has a 256-entry buffer. If the client reads too slowly, events are dropped rather than blocking the engine.

Source Management

RPCDescription
GetSourceInfoSource details including source-specific metadata

Administration

RPCDescription
ReloadTableTrigger re-baseline for a specific table
ReloadAllTrigger re-baseline for all tables (optionally on a specific source)
PauseSyncPause sync (all or specific source)
ResumeSyncResume sync
ResetSourceDrop and recreate replication slot (and optionally publication)

Configuration (read-only)

RPCDescription
ListTablesList configured tables with sources and targets
GetTableSchemaColumn definitions for a table

Snapshot Management

RPCDescription
CreateSnapshotTrigger snapshot creation
ListSnapshotsList available snapshots
InspectSnapshotSnapshot metadata and table summaries
RestoreSnapshotRestore from a snapshot
DeleteSnapshotDelete a snapshot
PruneSnapshotsDelete all but N most recent snapshots

Dead Letter Management

RPCDescription
ListDeadLettersList dead letters for a pipeline
ReplayDeadLettersRe-deliver dead letters to the target
PurgeDeadLettersRemove dead letters

Replay

RPCDescription
StartReplayStart replaying a snapshot through a target
GetReplayStatusCheck replay progress
StopReplayStop an active replay

Query Service (laredo.v1.TableSyncQuery)

Port: 4001 (same server as OAM)

RPCDescription
LookupSingle-row lookup on a unique index
LookupAllMulti-row lookup on a non-unique index
GetRowDirect primary key access
ListRowsPaginated row listing
CountRowsRow count
SubscribeServer-streaming change events with optional replay

Subscribe

Server-streaming RPC that pushes change events for a table. Optionally replays all existing rows as INSERT events before switching to live changes.

rpc Subscribe(SubscribeRequest) returns (stream SubscribeResponse);

Request fields:

FieldTypeDescription
schemastringSchema name (required).
tablestringTable name (required).
replay_existingboolIf true, all current rows are sent as INSERT events before live streaming begins.

Response fields:

Each SubscribeResponse represents a single change event:

FieldTypeDescription
actionstringOne of INSERT, UPDATE, DELETE, or TRUNCATE.
positionstringSource position (e.g., WAL LSN) if available.
xidint64Transaction ID if available.
timestampTimestampWhen the event was generated.
old_valuesStructPrevious row values (present for UPDATE and DELETE).
new_valuesStructNew row values (present for INSERT and UPDATE).

Replay behavior: When replay_existing is true, the server iterates over all current rows in the indexed target and sends each as an INSERT event. The listener is registered before replay begins, so changes that occur during replay are queued and delivered after replay completes. No deduplication is performed -- if a row is modified during replay, the client may see it in both the replay and the live stream.

Backpressure: Each subscriber has a 256-entry buffer. If the client consumes too slowly, live events are dropped.

Replication Service (laredo.replication.v1.TableSyncReplication)

Port: 4002 (per fan-out target, configurable)

The Replication service implements the fan-out replication protocol. It allows downstream Laredo instances (or any gRPC client) to replicate table data from an upstream server. Each fan-out target maintains an in-memory copy of the table rows and a change journal, enabling clients to connect, catch up from a known position, and then receive live changes.

RPCTypeDescription
Syncserver-streamingPrimary replication stream (handshake, snapshot/delta, live)
ListSnapshotsunaryAvailable snapshots for client bootstrapping
FetchSnapshotserver-streamingStreaming download of a specific snapshot
GetReplicationStatusunaryCurrent sequence, journal bounds, connected clients

Sync

The primary server-streaming RPC for replication. A client connects, declares its current state, and receives catch-up data followed by a continuous live stream of changes.

rpc Sync(SyncRequest) returns (stream SyncResponse);

Request fields:

FieldTypeDescription
schemastringSchema name (required).
tablestringTable name (required).
last_known_sequenceint64The last sequence number the client has processed. 0 means the client has no state.
last_snapshot_idstringID of the last snapshot the client loaded (used for DELTA_FROM_SNAPSHOT mode).
client_idstringUnique identifier for this client. If empty, the server assigns an anonymous ID (anon-<timestamp>).

Protocol phases

The stream progresses through three phases:

1. Handshake

The server sends a single SyncHandshake message that tells the client which sync mode will be used and provides server state:

FieldTypeDescription
modeSyncModeThe sync mode selected by the server (see below).
server_current_sequenceint64The server's latest sequence number at the time of the handshake.
journal_oldest_sequenceint64The oldest sequence still available in the server's journal.
resume_from_sequenceint64The sequence the server will resume sending from.
columnsrepeated ColumnDefinitionTable column definitions (schema metadata).
snapshot_idstringSnapshot ID if the server is sending a snapshot.

SyncMode enum:

ValueMeaning
SYNC_MODE_FULL_SNAPSHOTClient has no state (or its state is too old for delta). Server sends a full snapshot followed by journal catch-up.
SYNC_MODE_DELTAClient's last_known_sequence is still within the journal window. Server sends only the journal entries since that sequence.
SYNC_MODE_DELTA_FROM_SNAPSHOTClient has a snapshot but needs journal entries applied on top of it.

The server selects the mode automatically:

  • If last_known_sequence > 0 and the sequence is still in the journal (i.e., >= journal_oldest_sequence), the server uses SYNC_MODE_DELTA.
  • Otherwise, the server uses SYNC_MODE_FULL_SNAPSHOT.

2. Snapshot (only for SYNC_MODE_FULL_SNAPSHOT)

If the mode is SYNC_MODE_FULL_SNAPSHOT, the server takes a point-in-time snapshot and streams it:

  1. SnapshotBegin -- contains the snapshot_id, the sequence at which the snapshot was taken, and the expected row_count.
  2. One SnapshotRow per row -- each contains the row data as a google.protobuf.Struct.
  3. SnapshotEnd -- contains the final sequence and the number of rows_sent.

After the snapshot, resume_from_sequence is set to the snapshot's sequence for the journal catch-up phase.

3. Journal catch-up and live streaming

After the handshake (and snapshot if applicable), the server sends all journal entries since resume_from_sequence as ReplicationJournalEntry messages. Once caught up, the stream transitions to live mode where new changes are sent as they occur.

Each ReplicationJournalEntry contains:

FieldTypeDescription
sequenceint64Monotonically increasing sequence number.
source_positionstringOriginal source position (e.g., PostgreSQL WAL LSN).
timestampTimestampWhen the change occurred.
actionstringOne of INSERT, UPDATE, DELETE, or TRUNCATE.
old_valuesStructPrevious row values (present for UPDATE and DELETE).
new_valuesStructNew row values (present for INSERT and UPDATE).

Heartbeats: During idle periods (no changes), the server sends periodic Heartbeat messages containing current_sequence and server_time. The default heartbeat interval is 5 seconds. Clients should treat a gap of 30 seconds without any message as a connection failure and reconnect.

Schema changes: If the table schema changes, the server sends a SchemaChangeNotification with old_columns and new_columns.

Client lifecycle: The server tracks each connected client's state. During catch-up, the client state is "catching_up". Once all journal entries have been delivered and the client is receiving live changes, the state transitions to "live". If the fan-out target has a max_clients limit configured and it has been reached, new Sync calls return RESOURCE_EXHAUSTED.

Stream message types summary

Message typePhaseDescription
SyncHandshakeHandshakeAlways first. Contains mode, server state, and schema.
SnapshotBeginSnapshotSignals start of snapshot data.
SnapshotRowSnapshotOne row of snapshot data.
SnapshotEndSnapshotSignals end of snapshot data.
ReplicationJournalEntryCatch-up / LiveA single change event.
SchemaChangeNotificationLiveTable schema changed.
HeartbeatLivePeriodic keepalive during idle periods.

ListSnapshots

Returns available snapshots for a fan-out target. Clients can use these to bootstrap by fetching a snapshot via FetchSnapshot and then connecting with Sync using the snapshot's sequence.

rpc ListSnapshots(ListSnapshotsRequest) returns (ListSnapshotsResponse);

Request fields:

FieldTypeDescription
schemastringSchema name (required).
tablestringTable name (required).
limitint32Maximum number of snapshots to return. 0 means no limit.

Response fields:

The response contains a repeated ReplicationSnapshotInfo snapshots array. Each entry:

FieldTypeDescription
snapshot_idstringUnique identifier for this snapshot.
sequenceint64The journal sequence at the time the snapshot was taken.
source_positionstringOriginal source position at snapshot time.
row_countint64Number of rows in the snapshot.
size_bytesint64Approximate size of the snapshot in bytes.
created_atTimestampWhen the snapshot was created.

FetchSnapshot

Streams the contents of a specific snapshot to the client. This is a server-streaming RPC that sends the snapshot in three phases: begin, rows, end.

rpc FetchSnapshot(FetchSnapshotRequest) returns (stream FetchSnapshotResponse);

Request fields:

FieldTypeDescription
snapshot_idstringID of the snapshot to fetch (required). Obtained from ListSnapshots.

Stream format:

Each FetchSnapshotResponse contains exactly one of:

Chunk typeFieldsDescription
SnapshotBeginsnapshot_id, sequence, row_countFirst message. Tells the client which snapshot this is, the sequence at which it was taken, and how many rows to expect.
SnapshotRowrow (Struct)One row of data. Sent row_count times.
SnapshotEndsequence, rows_sentFinal message. Confirms the sequence and actual number of rows sent.

The server searches all fan-out targets across all sources to find the requested snapshot. Returns NOT_FOUND if the snapshot ID does not exist.

GetReplicationStatus

Returns the current state of a fan-out target's replication infrastructure, including the journal, connected clients, and latest snapshot.

rpc GetReplicationStatus(GetReplicationStatusRequest) returns (GetReplicationStatusResponse);

Request fields:

FieldTypeDescription
schemastringSchema name (required).
tablestringTable name (required).

Response fields:

FieldTypeDescription
current_sequenceint64The latest sequence number in the journal.
journal_oldest_sequenceint64The oldest sequence still retained in the journal. Clients with a last_known_sequence older than this must do a full snapshot sync.
journal_entry_countint64Number of entries currently in the journal.
row_countint64Total number of rows in the fan-out target's in-memory state.
connected_clientsint32Number of currently connected Sync clients.
clientsrepeated ConnectedClientPer-client details (see below).
latest_snapshotReplicationSnapshotInfoInformation about the most recent snapshot, if one exists.

ConnectedClient fields:

FieldTypeDescription
client_idstringThe client's identifier (provided in SyncRequest or auto-assigned).
current_sequenceint64The last sequence number delivered to this client.
behind_countint64How many journal entries behind the server this client is.
buffer_depthint32Number of entries queued in the client's send buffer.
connected_atTimestampWhen the client connected.
statestringClient state: "catching_up" or "live".

Returns NOT_FOUND if no fan-out target exists for the given schema and table.

Error Codes

All services use standard gRPC status codes:

CodeWhen
INVALID_ARGUMENTMissing or malformed required fields.
NOT_FOUNDRequested resource (table, pipeline, snapshot, fan-out target) does not exist.
FAILED_PRECONDITIONOperation requires a precondition that is not met (e.g., confirm=true for destructive operations, snapshot store not configured).
RESOURCE_EXHAUSTEDFan-out target has reached its maximum client limit.
INTERNALServer-side error (e.g., snapshot store I/O failure).
UNIMPLEMENTEDRPC exists in the proto but is not yet implemented.