ADR 0055 — Reasonator API contract: REST with sync + async endpoints
Status: Accepted
Date: 2026-05-09 UTC
Refs: #1385, docs/architecture/reasonator/design.md (Decision 2), docs/architecture/reasonator/api-contract.md
Context
Reasonator must serve two distinct calling patterns:
1. Pro+ tier: synchronous, low-latency scoring during a live trade ticket session (latency target p99 < 2s for ≤10 headlines).
2. Pro tier: asynchronous, bulk scoring of potentially thousands of unscored sentiment_events rows as a background job.
Three interface styles were evaluated: REST, gRPC, and message queue (SQS/pub-sub only).
Decision
REST, with two endpoint shapes:
- POST /v1/score — synchronous, returns scored results inline. For Pro+ real-time path and small Pro batches.
- POST /v1/score/batch + GET /v1/score/batch/{job_id} — asynchronous job pattern. For Pro background bulk scoring. Caller submits, Reasonator queues, caller polls.
- POST /v1/score/rescore — synchronous re-scoring with a new model SHA. For model upgrade sweeps.
Full contract in docs/architecture/reasonator/api-contract.md.
Consequences
- Positive: REST is the existing API style across Raptor. No new client-generation tooling. Heroku + Flask make REST trivial.
- Positive: The sync/async split maps cleanly to the tier distinction: Pro+ → sync path, Pro → async path.
- Positive: The
job_ididempotency pattern (caller assigns UUID) makes the batch endpoint safe to retry without duplicate jobs. - Negative: REST polling (
GET /score/batch/{job_id}) adds round trips compared to a push-based queue. Acceptable at Phase 1 scale; at high throughput, SQS notification (push) would be more efficient. - Future: If Pro+ requires streaming sentiment updates (live tick-by-tick during a trade), gRPC server-streaming is the upgrade path. This is a Phase 3 consideration and does not change the REST interface for Phase 1.
Alternatives Considered
gRPC: Enables bidirectional streaming for a future Pro+ live feed. Adds client stub generation complexity, complicates Heroku routing (HTTP/2 and gRPC-web proxying). No advantage over REST at Phase 1 throughput. Deferred to Phase 3.
Message queue only (SQS): Cannot serve the Pro+ synchronous real-time path. A response would require a separate response queue and correlation IDs — equivalent complexity to REST polling with worse debuggability. Rejected for the sync path. Using SQS as the transport for Pro batch jobs was considered but is overkill at Phase 1 scale (the in-process job queue is sufficient).
GraphQL: No advantage for a machine-to-machine API. Rejected without further evaluation.