RPC contract
Package proposal: optima.v1. These signatures are design sketches. Ravi owns the real proto; Kai and Mira review it before client mocks or implementation.
Shared value shapes
Money { currency: ISO4217, minor_units: int64 }
Evidence { kind: supplied | inferred | user_asserted | unknown,
source_id, captured_at, confidence? }
Period { kind, local_start, local_end_exclusive, timezone, card_id? }
SnapshotMeta { ledger_version, calculation_revision,
state: ready | pending | unavailable, completeness, revised_at }
Allowance { rule_id, period, native_unit, used, limit?,
status: available | exhausted | conditional | unavailable,
spend_equivalent?, reasons[] }
PurchasePortions { covered_minor, no_bonus_minor, unclear_minor }
Native units distinguish SGD, bonus points, cashback minor units and miles. JSON int64 values remain strings on the wire; clients must not lose precision by converting arbitrary int64 to JavaScript Number. Unsupported enum variants render an unavailable state, not a success label.
Reads use Connect GET
All following RPCs are unary and annotated idempotency_level = NO_SIDE_EFFECTS. Configure the generated Connect client to use GET. Authenticate every request and mark personal responses private/no-store. Do not put forwarding secrets or merchant text in GET requests. Session validation does not slide expiry during these reads; renewal is an explicit POST.
| RPC | Request | Response |
|---|---|---|
GetSession |
empty | Signed-in account summary or unauthenticated |
GetSetup |
empty | Cards, masked forwarding destination, test status |
ListCardProducts |
issuer, cursor | Verified/support-status catalogue; max 50 |
GetHome |
reporting_month | One snapshot with cards, split, rewards, review count |
GetCard |
owned_card_id, period | Identity, allowances, gates and summary |
ListTransactions |
selection, cursor, revision | Max 50 rows, next cursor, metadata |
GetTransaction |
transaction_id, revision? | Fact, current correction, portions, provenance and explanations |
ListReviewItems |
cursor | Max 50 held or unclear items and reasons |
GetHistory |
filter, six_period_window | Six chart rows and matching table; earlier-window cursor |
GetPeriod |
exact period key, revision? | Period totals, credits, card summaries and transaction cursor |
GetRevision |
revision_id | Explainable changed inputs and affected periods |
The protocol defines GET for methods marked as having no side effects. Authentication and cache policy remain application responsibilities. Connect protocol.
Commands use POST
| RPC | Input and concurrency | Result |
|---|---|---|
CreateCard |
product, last_four, statement day, command_id | Owned card and version |
UpdateCardSettings |
dated settings, expected_version, command_id | New settings version and pending recalculation |
UpdatePriority |
ordered owned-card IDs, expected_version, command_id | Saved order |
CorrectTransaction |
transaction_id, typed patch, expected_version, command_id | Corrected fact, pending snapshot and job status |
ResolveCapture |
held capture ID, explicit card/amount/currency choice, expected_version | Confirmed fact or kept-excluded state |
RecordCreditCorrection |
amount, currency, optional original purchase, reason, command_id | Credit fact, optional link, pending calculation |
RevealForwardingDestination |
recent-auth proof | Destination, no-store; never included in analytics |
RotateForwardingDestination |
recent auth, expected_version, command_id | New token; old token revoked atomically |
RenewSession / SignOut |
CSRF protection | New expiry / revoked session |
RequestAccountDeletion |
recent auth, explicit confirmation, command_id | Access revoked and opaque deletion request ID |
Store command result and request digest under (account_id, command_id) in the same transaction as the change. Reusing a key with a different body fails. Retain command receipts for a proposed 48-hour retry window; make that window part of the client contract. Identity comes from the server session, never an account ID in the request.
Errors and response limits
unauthenticated: sign in again, preserve local form entries without shared storage.not_found: missing or not owned; avoid revealing another account's record existence.invalid_argument: field errors, including invalid period or ambiguous amount.aborted: record version conflict; show latest value before retry.resource_exhausted: quota reached, retry time and reason; no automatic busy loop.unavailable: safe retry for reads, command retry with the same command ID.failed_precondition: unsupported product/operator, expired revision cursor or deletion state.
Proposed limits: 64 KiB command bodies, 256 KiB responses, 50 transaction rows, six chart periods per request. History remains reachable through pagination; limits do not delete older history. Cursors are opaque, signed and bound to account, selection and revision.
Compatibility and rollout
Add fields and enum variants; do not reuse tags or remove fields. Reserve retired tags and names. New behaviour uses a new RPC or package version. CI runs buf lint and buf breaking against main and the last production schema. Backend deploys first. Additive database expansion precedes compatible backend and frontend deployment; contraction requires all callers migrated and a separately reviewed release.