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 APIClient API
AudienceYour backend / agent orchestratorEnd-user devices
AuthAuthorization: Bearer APP_TOKENOne-time signed device registration, then a session id on every message
CallsUnary RPCs — RouteDecision, GetDecisionOutcome, GetQuota, GetIdentityDevicesOne bi-directional stream — authenticate, subscribe, receive, ack, outcome
WiregRPC over TLSgRPC 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
        ▼                    ▼                    ▼
                          EXPIRED

Every decision moves through this state machine, with QUEUEDDELIVEREDACKNOWLEDGED → 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.