Core Concepts
Architecture
How encrypted decisions move from your agent, through the Noxy relay, to user devices — and how outcomes come back. A practical view of the moving parts you integrate with.
The shape of the system
┌───────────────┐ gRPC (TLS) ┌───────────────┐ gRPC / WSS ┌───────────────┐ │ Your agent / │─ RouteDecision ─►│ Noxy relay │─ encrypted ──►│ User device │ │ backend │ │ │ decision event│ (Browser / iOS│ │ (Agent SDK) │◄─ outcome poll ──│ciphertext only│◄-- Outcome ── │ Android / TG)│ └───────────────┘ └───────────────┘ └───────────────┘
There are exactly three roles in the picture: your agent, the Noxy relay, and one or more user devices. Agents use the Agent API; devices use the Client API. Both APIs share a single relay endpoint over HTTP/2.
Two service surfaces
| Agent API | Client API | |
|---|---|---|
| Audience | Your backend / agent orchestrator | End-user devices |
| Auth | Authorization: Bearer APP_TOKEN | One-time signed device registration, then a session id on every message |
| Calls | Unary RPCs — RouteDecision, GetDecisionOutcome, GetQuota, GetIdentityDevices | One bi-directional stream — authenticate, subscribe, receive, ack, outcome |
| Wire | gRPC over TLS | gRPC over TLS or WebSocket (JSON, camelCase) |
Agent SDKs (Node.js, Python, Go, Rust) wrap the unary RPCs. Client SDKs (Browser, iOS, Android, Telegram Bot) wrap the streaming surface with platform-appropriate storage and lifecycle handling.
Identity → device fan-out
A decision is routed to an identity, not to a single device. An identity can have many devices registered against it — a laptop, a phone, a Telegram session — and the relay encrypts a separate ciphertext for every one of them. The first device to answer Approve or Reject closes the decision for that identity. Other devices receive a soft-cancel.
Identity is typed (wallet, email, phone, or your own user_id). See Identity Types for the full breakdown.
Decision lifecycle
RouteDecision delivery device receives
│ │ │
▼ ▼ ▼
┌────────┐ online ┌───────────┐ ack ┌──────────────┐ user ┌────────────┐
│ QUEUED ├──────────► DELIVERED ├──────►│ ACKNOWLEDGED ├────────► APPROVED / │
│ │ offline+ │ │ │ │ decides│ REJECTED │
└────┬───┘ wake └─────┬─────┘ └──────┬───────┘ └────────────┘
│ │ │
│ ttl expires │ ttl expires │ ttl expires
▼ ▼ ▼
EXPIREDEvery decision moves through this state machine, with QUEUED → DELIVERED → ACKNOWLEDGED → terminal. The terminal state is one of APPROVED, REJECTED, or EXPIRED. The relay also persists rows for audit, so you can re-query an outcome by id.
Real-time and store-and-forward
The relay always persists a decision first, then attempts live delivery. If the device is online it gets the payload on its open stream. If it is offline the relay can send a silent APNs wake on iOS or a data-only FCM push on Android. The device reconnects, drains its queue, and submits an outcome.
What the relay sees and does not see
- Sees: your
APP_ID, the identity the decision is routed to, the device's public keys, the ciphertext, and the lifecycle metadata (status, timestamps, request ids). - Does not see: the plaintext of your decision payload, the user's response content beyond Approve/Reject, or any key material from your agent.
The Agent SDK performs the per-device encryption locally before the bytes ever leave your process. Details on the construction live in Encryption & Security.
Why it is split this way
- Symmetric authorisation. Agents authenticate with a token; users authenticate by signing once. Both surfaces have independent rate limits.
- Resilient delivery. Decisions are durable. A flaky network or a backgrounded app does not lose them.
- End-to-end secrecy. The relay is operationally minimal — it routes ciphertext. Even Noxy operators cannot read your decisions.