AeroNyx Chat API Integration Guide
Send and receive end-to-end encrypted AeroNyx messages over HTTPS: request signing, a runnable quick start, the sealed envelope format, error codes and test vectors.
The AeroNyx Chat API lets a server, bot or application send and receive end-to-end encrypted one-to-one messages with AeroNyx users over ordinary HTTPS. It does not use the AeroNyx Privacy Network, VPN, onion routing or anonymous mailbox.
Sending is immediate: if the recipient's App is connected, your message reaches
it at once. Receiving through this API works by polling: GET /inbox/ returns
whatever is waiting and never holds the request open. To receive in real time,
connect the same identity to the Chat Relay WebSocket (wss://api.aeronyx.network/ws/relay/). See
Real-time delivery.
Base URL: https://api.aeronyx.network/api/relay/developer/v1
How it works
- Create an identity. An Ed25519 key pair is your chat identity. The 32-byte public key, written as 64 lowercase hexadecimal characters, is your chat address. No AeroNyx account is needed.
- Sign every request. Each call carries an
Authorizationheader signed with your key (Authentication). - Seal each message. Encrypt and sign the message on your side and send only the resulting envelope (Message envelope).
- Send it with
POST /messages/. - Receive with
GET /inbox/, decrypt and store each message, then confirm it withPOST /acks/.
| Endpoint | Authentication | Purpose |
|---|---|---|
GET /capabilities/ | none | Service limits and supported features |
POST /messages/ | required | Send a sealed message |
GET /inbox/ | required | Fetch unacknowledged messages |
POST /acks/ | required | Confirm messages you have stored |
Quick start
This script sends a message from one new identity to another, reads it back,
decrypts it and acknowledges it. Save the
reference implementation below as
aeronyx_chat.py, install the dependencies with pip install pynacl requests,
and run it.
from __future__ import annotations
import hashlib, json, os, time, uuid
import requests
from nacl.signing import SigningKey
from aeronyx_chat import open_, seal
BASE = "https://api.aeronyx.network"
PREFIX = "/api/relay/developer/v1"
def call(key: SigningKey, method: str, path: str, body: dict | None = None):
"""Signs and sends one request. `path` is the full path after the host."""
raw = b"" if body is None else json.dumps(body, separators=(",", ":")).encode()
seconds = str(int(time.time()))
nonce = os.urandom(16)
digest = hashlib.sha256(
b"AeroNyx/DeveloperChatRequestV1\0" + method.encode() + b"\0"
+ path.encode() + b"\0" + seconds.encode() + b"\0"
+ nonce + hashlib.sha256(raw).digest()
).digest()
signature = key.sign(digest).signature.hex()
pub = key.verify_key.encode().hex()
headers = {
"Authorization": f"AeroNyx-Chat-V1 {pub}:{seconds}:{nonce.hex()}:{signature}",
"Content-Type": "application/json",
}
# Send exactly the bytes that were signed.
return requests.request(method, BASE + path, data=raw or None,
headers=headers, timeout=20)
alice, bob = SigningKey.generate(), SigningKey.generate()
bob_address = bob.verify_key.encode().hex()
# 1. Alice seals and sends a message to Bob.
message_id = uuid.uuid4()
now = int(time.time())
sent = call(alice, "POST", PREFIX + "/messages/", {
"receiver_pubkey": bob_address,
"message_id": str(message_id),
"discriminant": 11,
"sent_at": now,
"payload_b64": seal(alice, bob.verify_key.encode(), "Hello Bob", message_id, now),
})
print("send:", sent.status_code, sent.json())
# 2. Bob fetches his inbox, decrypts, and then acknowledges.
inbox = call(bob, "GET", PREFIX + "/inbox/?limit=50").json()
for item in inbox["messages"]:
opened = open_(bob, item["payload_b64"])
print("received:", opened["plaintext"], "from", opened["sender"][:8])
# Store the message durably here, then acknowledge it.
ack = call(bob, "POST", PREFIX + "/acks/", {"message_ids": [item["msg_id"]]})
print("ack:", ack.status_code, ack.json())
Authentication
Every endpoint except GET /capabilities/ requires this header:
Authorization: AeroNyx-Chat-V1 <public-key-hex>:<unix-seconds>:<nonce-hex>:<signature-hex>
public-key-hexis your 64-character chat address.unix-secondsmust be within 60 seconds of the server clock.nonce-hexis 16 random bytes written as 32 lowercase hex characters. Each nonce can be used once.signature-hexis an Ed25519 signature, written as 128 hex characters, over this 32-byte digest:
SHA256(
"AeroNyx/DeveloperChatRequestV1\0" ||
UPPERCASE_HTTP_METHOD || "\0" ||
PATH_WITH_QUERY || "\0" ||
ASCII_UNIX_SECONDS || "\0" ||
NONCE_BYTES ||
SHA256(EXACT_HTTP_BODY_BYTES)
)
PATH_WITH_QUERY is the full request path as sent, including the
/api/relay/developer/v1 prefix and any query string. For example:
| Request | PATH_WITH_QUERY to sign |
|---|---|
POST https://api.aeronyx.network/api/relay/developer/v1/messages/ | /api/relay/developer/v1/messages/ |
GET https://api.aeronyx.network/api/relay/developer/v1/inbox/?limit=50 | /api/relay/developer/v1/inbox/?limit=50 |
EXACT_HTTP_BODY_BYTES are the bytes of the request body exactly as sent. JSON
whitespace and key order therefore matter: serialize once, sign those bytes, and
send those same bytes. A request without a body signs the SHA-256 of the empty
string.
Authentication test vector
Use this to check your signing code before you call the API. The key is for testing only; never use it for real messages.
{
"seed_hex": "0303030303030303030303030303030303030303030303030303030303030303",
"public_key_hex": "ed4928c628d1c2c6eae90338905995612959273a5c63f93636c14614ac8737d1",
"method": "POST",
"path_with_query": "/api/relay/developer/v1/messages/",
"unix_seconds": "1780000000",
"nonce_hex": "000102030405060708090a0b0c0d0e0f",
"body": "{\"receiver_pubkey\":\"8139770ea87d175f56a35466c34c7ecccb8d8a91b4ee37a25df60f5b8fc9b394\",\"message_id\":\"018f5164-c1b2-7e2b-9d8a-2c60597f4c2f\",\"discriminant\":11,\"sent_at\":1780000000,\"payload_b64\":\"AAAA\"}",
"body_sha256_hex": "1971a2ef86b2a085e033fd65313981c8668204d49cedbe530875036d1f45c9cc",
"digest_hex": "6f4d0ca6dcd1221780b2dae45dd94e4bae7e79f266e8bb69f2d50c20de08b276",
"signature_hex": "14ed2108e1ecf8ce5d04299e59af6d00dec218d62592de127e6a640f235ea4e9703004561fc08e929d0159f92fc51d35a881eec7d0eb16f19d94270c6262cb0c",
"authorization": "AeroNyx-Chat-V1 ed4928c628d1c2c6eae90338905995612959273a5c63f93636c14614ac8737d1:1780000000:000102030405060708090a0b0c0d0e0f:14ed2108e1ecf8ce5d04299e59af6d00dec218d62592de127e6a640f235ea4e9703004561fc08e929d0159f92fc51d35a881eec7d0eb16f19d94270c6262cb0c"
}
Ed25519 signatures are deterministic, so the same inputs must give exactly this
signature_hex. If your body_sha256_hex differs, your body bytes differ.
Troubleshooting 401
The API answers every authentication failure with the same 401, so check
these in order:
- The clock:
unix-secondsis within 60 seconds of real time. - The nonce is new for every request, 16 bytes, written as 32 lowercase hex characters.
- The signed path includes
/api/relay/developer/v1and the query string. - The signed method is uppercase and matches the request.
- The signed body bytes are the bytes you send. Do not re-serialize the JSON after signing.
- Your digest and signature match the test vector above.
Endpoints
Service information
GET /capabilities/ needs no authentication and returns the current limits and
supported features.
Send a message
POST /messages/
{
"receiver_pubkey": "<64 lowercase hex characters>",
"message_id": "018f5164-c1b2-7e2b-9d8a-2c60597f4c2f",
"discriminant": 11,
"sent_at": 1780000000,
"payload_b64": "<sealed envelope, Base64>"
}
All five fields are required and no others are allowed.
receiver_pubkeyis the recipient's chat address. It must differ from your own.message_idis a UUID. Use a new random UUID for each message and keep the exact body for retries. Sending the samemessage_idwith different fields returns409 idempotency_conflict.discriminantis11for a one-to-one message that an AeroNyx App user should see.sent_atis Unix seconds and must be within 300 seconds of server time.payload_b64is the sealed envelope described in Message envelope.
The relay checks only that payload_b64 is valid Base64 of 1 byte to 191 KiB.
It cannot tell an envelope from other data, so a payload that is not a valid
envelope is accepted here and silently dropped by the recipient's App. Test your
envelope against the golden vector.
A successful request returns HTTP 202:
{
"message_id": "018f5164-c1b2-7e2b-9d8a-2c60597f4c2f",
"accepted": true,
"delivered_live": false
}
accepted means the relay stored the ciphertext durably. delivered_live is
true when the relay handed the message to live delivery without an error. It
is true even when the recipient has no connection open, so it does not tell
you whether anyone is online. Neither field means the recipient has decrypted or
displayed the message; a delivery receipt in your inbox does
(Events in the inbox).
Fetch the inbox
GET /inbox/?limit=50
Returns up to 100 unacknowledged events. The same event can appear again after a timeout or a restart.
{
"messages": [{
"sender_pubkey": "<hex>",
"discriminant": 11,
"payload_b64": "<ciphertext>",
"timestamp": 1780000000,
"msg_id": "018f5164-c1b2-7e2b-9d8a-2c60597f4c2f"
}],
"has_more": false
}
If has_more is true, acknowledge what you have stored and fetch again.
Events in the inbox
The inbox holds everything the relay has queued for your identity, not only new
messages. An item without control_type is a message: open payload_b64 as
described in Message envelope. An item with control_type
is an event about a message. Check control_type before decrypting anything:
receipts have no payload_b64.
control_type | Meaning | msg_id |
|---|---|---|
message_receipt | The recipient's App decrypted and stored a message you sent. | The message_id of your message |
message_read | The recipient read your message. Sent only when their read receipts are on. | The message_id of your message |
message_edit | The sender edited a message (target_msg_id, the same ID). | The edited message's ID |
message_revoke | The sender withdrew a message. | The withdrawn message's ID |
message_reaction | A reaction to target_msg_id was added or removed. | The reaction's ID |
Edits, withdrawals and reactions are signed (payload_sig); their formats are
described in AeroNyx Chat Relay Client Integration. Acknowledging an ID removes every queued item that carries
it, so process a whole page before you acknowledge. Acknowledge event types you
do not handle as well; otherwise they are returned again.
Acknowledge messages
POST /acks/
{
"message_ids": ["018f5164-c1b2-7e2b-9d8a-2c60597f4c2f"]
}
Acknowledge a message only after you have validated it, decrypted it and written
it to durable storage. Unknown IDs are left out of the acknowledged list in
the response.
Delivery model
- Delivery is at least once. Deduplicate by
control_typetogether withmsg_id: a receipt carries the ID of the message it confirms. - Undelivered ciphertext is kept for 72 hours. Do not treat the service as permanent history.
- Retry a failed send with the exact same body and a fresh authentication nonce.
Real-time delivery
Sending
POST /messages/ hands your message to every connection the recipient has open
and, at the same time, keeps it queued until the recipient's App acknowledges
it. A recipient whose App is connected receives it at once; one who is offline
receives it the next time the App connects, within 72 hours.
Messages sent through this API do not trigger a push notification. A recipient whose App is closed is not alerted and sees the message the next time they open the App.
Receiving by polling
GET /inbox/ returns at once, whether or not anything is waiting. A message
reaches you only when you next call it, so the delay you see is your polling
interval. For conversations, receive over the WebSocket instead.
Receiving in real time
Connect the same identity to the Chat Relay WebSocket. No account or registration is needed.
- Open
wss://api.aeronyx.network/ws/relay/and log in with the same Ed25519 key, as described under Login in AeroNyx Chat Relay Client Integration. - After login the relay first replays everything still queued for you
(
from_offlineistrue), then sends each new item as soon as it arrives. Messages arrive asrelay_envelopeframes with the same fields as an inbox message; receipts, edits, withdrawals and reactions arrive as frames whosetypeis thecontrol_typeshown above. - Once you have stored an item, acknowledge it:
{"type": "relay_offline_ack", "msg_id": "018f5164-c1b2-7e2b-9d8a-2c60597f4c2f"}
The WebSocket and GET /inbox/ read the same queue: an item acknowledged on one
is gone from the other, and POST /acks/ works for both. You can keep sending
over HTTPS while you receive over the WebSocket.
Limits and errors
| Limit | Value |
|---|---|
| Sealed payload | 191 KiB (the Base64 JSON request stays below the request limit) |
| Request body | 256 KiB |
| Inbox page and acknowledgement batch | 100 items |
| Offline retention | 72 hours |
| Status | Meaning |
|---|---|
400 invalid_json | The body is not a JSON object. |
400 invalid_request | The body does not contain exactly the five fields, receiver_pubkey is not 64 lowercase hex characters or equals your own address, discriminant is outside 1-255, message_id is not a UUID, or payload_b64 is not valid Base64. |
400 invalid_payload | The decoded payload is empty or larger than 191 KiB. |
400 message_expired | sent_at differs from server time by more than 300 seconds. |
401 | Authentication is missing, expired, invalid or replayed. See Troubleshooting 401. |
403 | The recipient's policy rejected this sender. |
409 | Idempotency conflict, or an identical request is still in progress. |
429 | Sender rate limit reached. Wait for the time in the Retry-After header. |
503 | Durable delivery or authentication is temporarily unavailable. Retry the exact body with a fresh nonce. |
Privacy and visibility
Message content is end-to-end encrypted: it is sealed on your side with a key derived from the sender's and recipient's identities, which AeroNyx never holds, and the API stores and forwards only the ciphertext. To deliver messages the service can see the sender and receiver public keys, message time, size, delivery state and the IP address of each request. HTTPS does not hide this metadata from the service.
Message envelope
Messages that an AeroNyx App user should see use discriminant: 11 and an
end-to-end sealed envelope in payload_b64. The relay never sees the key or
the plaintext. All integers are little-endian.
1. Shared key
Both sides derive the same 32-byte key from their Ed25519 chat identities:
my_x25519_secret = clamp(SHA-512(my_ed25519_seed)[0..32])
peer_x25519 = Edwards-to-Montgomery(peer_ed25519_public_key)
raw = X25519(my_x25519_secret, peer_x25519)
key = HKDF-SHA256(salt = empty, ikm = raw, info = "AERONYX-P2P-KEY", length = 32)
With libsodium this is crypto_sign_ed25519_sk_to_curve25519,
crypto_sign_ed25519_pk_to_curve25519 and crypto_scalarmult, followed by
HKDF. Senders always use the HKDF key. Very old App versions sent with raw
directly, so a receiver may try raw if the HKDF key fails to decrypt.
2. Plaintext
A text message is its UTF-8 text. Messages from the App that carry
attachments, replies or forwards are a JSON object with
"type": "aeronyx_message" and a text field; render text if you do not
support the rest.
3. Encryption
XChaCha20-Poly1305 with a fresh random 24-byte nonce and no associated data.
ciphertext below is the cipher output including its 16-byte tag.
4. Signature
The sender signs these 121 bytes with Ed25519:
sender_pubkey 32 bytes
message_id 16 bytes (the raw bytes of the UUID sent as message_id)
receiver_pubkey 32 bytes
timestamp 8 bytes (u64, Unix seconds; use the same value as sent_at)
content_type 1 byte (0 = text)
SHA-256(ciphertext) 32 bytes
5. Envelope
message_id 16 bytes
sender_pubkey 32 bytes
receiver_pubkey 32 bytes
timestamp 8 bytes (u64)
ciphertext_len 8 bytes (u64)
ciphertext ciphertext_len bytes
nonce 24 bytes
content_type 4 bytes (u32, 0 = text)
signature 64 bytes
payload_b64 is the standard Base64 of these bytes. Note that content_type
is 1 byte inside the signed data but 4 bytes in the envelope.
Receiving
Parse the envelope, check that receiver_pubkey is your identity, verify the
signature against the envelope's sender_pubkey, then decrypt with the key
derived from that sender. Drop the message if any step fails. The App drops
live messages whose timestamp is more than 300 seconds from its clock.
Reference implementation (Python)
"""AeroNyx 1:1 chat envelope (discriminant 11). Requires PyNaCl."""
from __future__ import annotations
import base64
import hashlib
import hmac
import os
import struct
import uuid
from nacl.bindings import (
crypto_aead_xchacha20poly1305_ietf_decrypt,
crypto_aead_xchacha20poly1305_ietf_encrypt,
crypto_scalarmult,
crypto_sign_ed25519_pk_to_curve25519,
crypto_sign_ed25519_sk_to_curve25519,
)
from nacl.signing import SigningKey, VerifyKey
CONTENT_TEXT = 0
_HKDF_INFO = b"AERONYX-P2P-KEY"
def _hkdf_sha256(ikm: bytes, info: bytes, length: int = 32) -> bytes:
# RFC 5869 with an empty salt (= 32 zero bytes).
prk = hmac.new(b"\x00" * 32, ikm, hashlib.sha256).digest()
okm, block, counter = b"", b"", 1
while len(okm) < length:
block = hmac.new(prk, block + info + bytes([counter]), hashlib.sha256).digest()
okm += block
counter += 1
return okm[:length]
def chat_key(my_key: SigningKey, peer_public: bytes, hkdf: bool = True) -> bytes:
"""X25519 over the Ed25519 identities, then HKDF-SHA256."""
my_secret = bytes(my_key) + my_key.verify_key.encode() # libsodium 64-byte sk
raw = crypto_scalarmult(
crypto_sign_ed25519_sk_to_curve25519(my_secret),
crypto_sign_ed25519_pk_to_curve25519(peer_public),
)
return _hkdf_sha256(raw, _HKDF_INFO) if hkdf else raw
def _sign_data(sender, message_id, receiver, timestamp, content_type, ciphertext):
return (
sender
+ message_id
+ receiver
+ struct.pack("<Q", timestamp)
+ bytes([content_type]) # one byte here ...
+ hashlib.sha256(ciphertext).digest()
)
def seal(my_key: SigningKey, receiver: bytes, text: str, message_id: uuid.UUID,
timestamp: int, nonce: bytes | None = None) -> str:
"""Returns payload_b64 for POST /messages/ (discriminant 11)."""
nonce = nonce if nonce is not None else os.urandom(24)
key = chat_key(my_key, receiver)
ciphertext = crypto_aead_xchacha20poly1305_ietf_encrypt(
text.encode("utf-8"), None, nonce, key) # ciphertext || 16-byte tag, no AAD
sender = my_key.verify_key.encode()
mid = message_id.bytes
signature = my_key.sign(
_sign_data(sender, mid, receiver, timestamp, CONTENT_TEXT, ciphertext)
).signature
envelope = (
mid
+ sender
+ receiver
+ struct.pack("<Q", timestamp)
+ struct.pack("<Q", len(ciphertext))
+ ciphertext
+ nonce
+ struct.pack("<I", CONTENT_TEXT) # ... four bytes here
+ signature
)
return base64.b64encode(envelope).decode("ascii")
def open_(my_key: SigningKey, payload_b64: str) -> dict:
"""Verifies and decrypts an inbox payload addressed to my_key."""
env = base64.b64decode(payload_b64, validate=True)
if len(env) < 16 + 32 + 32 + 8 + 8 + 16 + 24 + 4 + 64:
raise ValueError("envelope too short")
mid, sender, receiver = env[0:16], env[16:48], env[48:80]
timestamp, ct_len = struct.unpack("<QQ", env[80:96])
if len(env) != 96 + ct_len + 24 + 4 + 64:
raise ValueError("envelope length mismatch")
ciphertext = env[96:96 + ct_len]
nonce = env[96 + ct_len:120 + ct_len]
(content_type,) = struct.unpack("<I", env[120 + ct_len:124 + ct_len])
signature = env[124 + ct_len:]
if receiver != my_key.verify_key.encode():
raise ValueError("envelope is addressed to another identity")
VerifyKey(sender).verify(
_sign_data(sender, mid, receiver, timestamp, content_type, ciphertext), signature)
# Current App versions use the HKDF key; very old ones sent with the raw key.
for hkdf in (True, False):
try:
plaintext = crypto_aead_xchacha20poly1305_ietf_decrypt(
ciphertext, None, nonce, chat_key(my_key, sender, hkdf))
break
except Exception: # noqa: BLE001 - try the legacy key next
plaintext = None
if plaintext is None:
raise ValueError("decryption failed")
return {
"message_id": str(uuid.UUID(bytes=mid)),
"sender": sender.hex(),
"timestamp": timestamp,
"content_type": content_type,
"plaintext": plaintext.decode("utf-8"),
}
Golden vector
Fixed keys, nonce, ID and time, for testing only. Never use these keys.
{
"sender_seed_hex": "0101010101010101010101010101010101010101010101010101010101010101",
"sender_pubkey": "8a88e3dd7409f195fd52db2d3cba5d72ca6709bf1d94121bf3748801b40f6f5c",
"receiver_seed_hex": "0202020202020202020202020202020202020202020202020202020202020202",
"receiver_pubkey": "8139770ea87d175f56a35466c34c7ecccb8d8a91b4ee37a25df60f5b8fc9b394",
"message_id": "018f5164-c1b2-7e2b-9d8a-2c60597f4c2f",
"timestamp": 1780000000,
"nonce_hex": "000102030405060708090a0b0c0d0e0f1011121314151617",
"plaintext": "Hello from AeroNyx",
"shared_key_hex": "dd08f8bb45a43c9bc186fdaf436d47e8e05b0aca44fa2bb9473769da0407652f",
"payload_b64": "AY9RZMGyfiudiixgWX9ML4qI4910CfGV/VLbLTy6XXLKZwm/HZQSG/N0iAG0D29cgTl3Dqh9F19Wo1Rmw0x+zMuNipG07jeiXfYPW4/Js5QApRhqAAAAACIAAAAAAAAA1vVyzdTx1kAdPEI7AGO12ppwLwaME0K9PJKd+u7OgWq0rwABAgMEBQYHCAkKCwwNDg8QERITFBUWFwAAAACAlpInhB5A9cD4LqKAT0OE0OpbMqT5w/xB8IiHrzuyg9LPjF9gDHdOekPNoHNxDMeu3Ift0UB69OGSofdfwKIN"
}
seal(alice, bob_public, "Hello from AeroNyx", message_id, 1780000000, nonce=bytes(range(24))) must produce exactly this payload_b64, and
open_(bob, payload_b64) must return the plaintext. This vector is verified
against the AeroNyx App's own key agreement, signer and cipher, and against the
node's strict envelope decoder.
Frequently asked questions
What is the AeroNyx Chat API?
It is a versioned HTTPS API for exchanging end-to-end encrypted one-to-one messages with AeroNyx users from a server, bot or application. It has four endpoints: capabilities, send, inbox and acknowledge. It carries only sealed envelopes; the content is encrypted on your side.
Do I need an AeroNyx account to use the API?
No. An Ed25519 key pair is the identity. Generate it locally and sign each request with it; the public key is your chat address.
Do I sign /messages/ or the full path?
The full path as sent, starting with /api/relay/developer/v1, plus the query string if there is one. Signing only /messages/ produces a 401. The authentication test vector on this page shows the exact inputs.
Why do I get 401?
Every authentication failure returns the same 401. The usual causes are a clock more than 60 seconds off, a reused nonce, a signed path or body that differs from what was sent, or a lowercase HTTP method in the signed data. Work through the checklist under Troubleshooting 401 and compare your digest with the test vector.
Why does the API return 202 but the message never appears in the AeroNyx App?
The relay accepts any valid Base64 payload, but the App only shows a sealed envelope that it can verify and decrypt. Check your payload_b64 against the golden vector on this page, make sure the envelope's receiver_pubkey is the recipient, and keep the envelope timestamp within 300 seconds of the current time. Sending plain text encoded as Base64 is accepted but never displayed.
Why does the API return invalid_request?
The most common causes are sending to your own public key, a body that does not contain exactly the five documented fields, a message_id that is not a UUID, or a discriminant outside 1-255. See the error table under Limits and errors.
Can AeroNyx read messages sent through this API?
No. Envelopes are encrypted with a key derived from the sender's and recipient's identities, which AeroNyx never holds. The service can see delivery metadata such as public keys, times, sizes and IP addresses.
How long are undelivered messages kept?
Up to 72 hours. Acknowledge each message only after you have decrypted and stored it; unacknowledged messages can be delivered again.
Which programming languages can I use?
Any language with Ed25519, X25519, HKDF-SHA256 and XChaCha20-Poly1305, which includes libsodium bindings for most languages. This page includes a complete Python reference implementation and a runnable quick start.
Is the AeroNyx Chat API real-time?
Sending is: a recipient whose App is connected gets your message at once. Receiving over HTTPS is polling, so you see new messages when you next call GET /inbox/. For real-time receiving, connect the same identity to the Chat Relay WebSocket at wss://api.aeronyx.network/ws/relay/; it reads the same queue.
Does an AeroNyx user get a notification for a message I send?
Not at present. Messages sent through this API do not trigger a push notification, so a user whose App is closed sees your message the next time they open it. A user whose App is connected sees it at once.
<!-- faq:end -->