API Reference
Agent & Client API
The wire shape your Agent SDK and Client SDK speak with the Noxy relay. Useful when you build a custom integration, debug a flow, or audit a payload.
Endpoint & transport
| Agent API | Client API | |
|---|---|---|
| Host | relay.noxy.network | |
| Transport | gRPC over HTTP/2 (TLS) | gRPC bi-di stream or WebSocket (JSON) over TLS |
| Auth | authorization: Bearer APP_TOKEN | First message: Authenticate or RegisterDevice; then a relay-issued session_id on every message |
| Wire format | Protobuf | Protobuf (gRPC) or JSON / camelCase (WSS) |
In normal use you call these through an SDK. The shapes below are the same ones the SDKs produce and consume; use them as a contract when extending.
Agent API
RouteDecision
Route an encrypted decision to a target device. Returns one delivery outcome per call.
Request
| Field | Type | Notes |
|---|---|---|
request_id | string | Stable per logical attempt; echoed in the response and in logs. |
ciphertext | bytes | AES-256-GCM ciphertext of the decision JSON. |
kyber_ct | bytes | Kyber768 encapsulation of the symmetric key against the target device. |
nonce | bytes | 12-byte random nonce used with AES-GCM. |
target_device_id | string | The device the envelope is encrypted for. |
ttl_seconds | uint32 | Time-to-live in seconds. Omit or pass 0 for the default (600 s). |
Response — DeliveryOutcome
| Field | Type | Notes |
|---|---|---|
request_id | string | Echo of your request id. |
decision_id | string | Use this with GetDecisionOutcome. Empty if the relay could not track human resolution (e.g. NO_DEVICES). |
status | enum | DELIVERED, QUEUED, NO_DEVICES, REJECTED, or ERROR. |
Tip. Agent SDKs handle per-device fan-out for you. If an identity has three devices you get back three delivery outcomes, one per ciphertext. Pick any non-empty decision_id to poll — outcomes are identity-wide.
GetDecisionOutcome
Identity-wide outcome of a previously routed decision.
Request
| Field | Type | Notes |
|---|---|---|
request_id | string | Stable per poll attempt. |
decision_id | string | From the original RouteDecision response. |
identity_id | string | The same logical id used to route the decision (e.g. email, wallet, user id). |
Response
| Field | Type | Notes |
|---|---|---|
pending | bool | true while no device has answered. |
outcome | enum | PENDING while in flight; one of APPROVED, REJECTED, EXPIRED when terminal. |
Poll until pending is false. SDKs do this with exponential backoff.
GetQuota
Quota and status for your application.
Response
| Field | Type |
|---|---|
app_name | string |
quota_total | uint64 |
quota_remaining | uint64 |
status | QUOTA_ACTIVE | QUOTA_SUSPENDED | QUOTA_DELETED |
GetIdentityDevices
List the devices currently registered for a logical identity.
Request
| Field | Type |
|---|---|
request_id | string |
identity_id | string |
Response — repeated devices
| Field | Type |
|---|---|
device_id | string |
public_key | bytes — classical public key |
pq_public_key | bytes — Kyber768 public key |
Client API
The Client API is a single bidirectional stream. The first message authenticates or registers the device; subsequent messages share a session_id issued by the relay.
Message envelope (all requests)
| Field | Type | Notes |
|---|---|---|
request_id | string | Stable per attempt. |
app_id | string | From the dashboard. |
device_id | string | Issued on first RegisterDevice or Authenticate response. |
session_id | string | Issued by the relay; required after first message. |
timestamp | int64 | Client-side milliseconds. |
nonce | bytes (12) | Per-message anti-replay nonce. |
Operations
| Operation | What it does |
|---|---|
RegisterDevice | One-time identity binding. Carries identity_type, identity_id, the registration HMAC signature, and the device's classical + post-quantum public keys. |
Authenticate | Reconnect with existing device keys. Returns the session id. |
SubscribeDecisions | Start receiving DecisionEvent frames (ciphertext, Kyber capsule, nonce). The SDK drains any backlog first. |
DecisionOutcome | Publish the user's answer: APPROVE or REJECT, with decision_id and received_at. |
RotateDeviceKeys | Replace the device's key pairs without re-registration. |
RevokeDevice | Remove the device permanently. Queued decisions are dropped. |
JSON over WebSocket
Browsers connect with WSS to the same host. All messages are JSON with camelCase fields and binary values (nonce, publicKey, signature, ciphertext) base64-encoded. The semantics are identical to gRPC; the wire is different only in encoding.
{
"requestId": "req-1",
"appId": "your-app-id",
"timestamp": 1731700000000,
"nonce": "<base64 12 bytes>",
"registerDevice": {
"identityType": "email",
"identityId": "user@example.com",
"signature": "<base64 HMAC>",
"devicePubkeys": {
"publicKey": "<base64>",
"pqPublicKey": "<base64>"
}
}
}Status codes & errors
| Where | What you'll see |
|---|---|
| HTTP / gRPC | Standard gRPC status codes — UNAUTHENTICATED for bad tokens, RESOURCE_EXHAUSTED for rate limits, UNAVAILABLE for transient errors. |
| Per-decision | The status field on DeliveryOutcome distinguishes QUEUED, DELIVERED, NO_DEVICES, REJECTED, and ERROR. |
| Per-poll | pending and outcome on GetDecisionOutcome; SDKs raise WaitForDecisionOutcomeTimeoutError after maxWaitMs. |
See Rate Limits for per-app and per-connection thresholds, Best Practices for retry and idempotency guidance, and Encryption & Security for the construction of the encrypted envelope.