AeroNyx 聊天 API 整合指南
透過 HTTPS 收發端對端加密的 AeroNyx 訊息:請求簽章、可直接執行的快速開始、已封裝的信封格式、錯誤碼與測試向量。
AeroNyx 聊天 API 讓伺服器、機器人或應用程式能透過一般的 HTTPS,與 AeroNyx 使用者收發端對端加密的一對一訊息。它不會使用 AeroNyx 隱私網路、VPN、洋蔥路由或匿名信箱。
傳送是即時的:收件者的 App 在線時,訊息會立刻送到。透過此 API 接收則是輪詢:GET /inbox/ 只回傳當下正在等待的內容,不會保持請求不斷開。若要即時接收,請讓同一個身分連線到聊天中繼 WebSocket(wss://api.aeronyx.network/ws/relay/),詳見下方「即時遞送」一節。
基礎網址:https://api.aeronyx.network/api/relay/developer/v1
運作方式
- 建立身分。 一組 Ed25519 金鑰對就是你的聊天身分。32 位元組的公鑰以 64 個小寫十六進位字元表示,即為你的聊天位址。不需要 AeroNyx 帳號。
- 為每個請求簽章。 每次呼叫都帶有以你的金鑰簽署的
Authorization標頭(見驗證)。 - 封裝每則訊息。 在你這一端加密並簽署訊息,只傳送產生的信封(見訊息信封)。
- 傳送: 使用
POST /messages/。 - 接收: 使用
GET /inbox/,解密並儲存每則訊息,再以POST /acks/確認。
| 端點 | 驗證 | 用途 |
|---|---|---|
GET /capabilities/ | 無 | 服務限制與支援的功能 |
POST /messages/ | 必要 | 傳送已封裝的訊息 |
GET /inbox/ | 必要 | 取得尚未確認的訊息 |
POST /acks/ | 必要 | 確認你已儲存的訊息 |
快速開始
這個腳本會從一個新身分傳送訊息給另一個新身分,讀回該訊息、解密並確認。請將下方的參考實作(Python)儲存為 aeronyx_chat.py,以 pip install pynacl requests 安裝相依套件,然後執行。
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())
驗證
除了 GET /capabilities/ 之外,每個端點都需要這個標頭:
Authorization: AeroNyx-Chat-V1 <public-key-hex>:<unix-seconds>:<nonce-hex>:<signature-hex>
public-key-hex是你的 64 字元聊天位址。unix-seconds必須與伺服器時鐘相差在 60 秒以內。nonce-hex是 16 個隨機位元組,以 32 個小寫十六進位字元表示。每個 nonce 只能使用一次。signature-hex是對下列 32 位元組摘要所做的 Ed25519 簽章,以 128 個十六進位字元表示:
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 是實際送出的完整請求路徑,包含 /api/relay/developer/v1 前綴與任何查詢字串。 例如:
| 請求 | 要簽署的 PATH_WITH_QUERY |
|---|---|
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 是請求本文實際送出的位元組。因此 JSON 的空白字元與鍵的順序都很重要:只序列化一次,簽署這些位元組,並送出完全相同的位元組。沒有本文的請求,簽署的是空字串的 SHA-256。
驗證測試向量
在呼叫 API 之前,請用它檢查你的簽章程式碼。這把金鑰僅供測試;請勿用於真實訊息。
{
"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 簽章具有決定性,因此相同的輸入必定得出完全一樣的 signature_hex。如果你的 body_sha256_hex 不同,就表示你的本文位元組不同。
排解 401
API 對所有驗證失敗都回應相同的 401,因此請依序檢查:
- 時鐘:
unix-seconds與實際時間相差在 60 秒以內。 - nonce 每個請求都是新的,長度為 16 位元組,並以 32 個小寫十六進位字元表示。
- 簽署的路徑包含
/api/relay/developer/v1與查詢字串。 - 簽署的方法為大寫,且與請求相符。
- 簽署的本文位元組就是你送出的位元組。簽署後不要重新序列化 JSON。
- 你的摘要與簽章和上方的測試向量一致。
端點
服務資訊
GET /capabilities/ 不需要驗證,會回傳目前的限制與支援的功能。
傳送訊息
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>"
}
五個欄位全部必填,且不允許其他欄位。
receiver_pubkey是收件者的聊天位址,不得與你自己的位址相同。message_id是 UUID。每則訊息請使用新的隨機 UUID,並保留完全相同的本文以供重試。以相同的message_id但不同的欄位傳送,會回傳409 idempotency_conflict。discriminant為11,代表 AeroNyx App 使用者應該會看到的一對一訊息。sent_at是 Unix 秒數,必須與伺服器時間相差在 300 秒以內。payload_b64是訊息信封中所述的已封裝信封。
中繼只檢查 payload_b64 是否為 1 位元組至 191 KiB 的有效 Base64。它無法分辨信封與其他資料,因此不是有效信封的酬載在這裡會被接受,並由收件者的 App 靜默丟棄。請用黃金向量測試你的信封。
成功的請求會回傳 HTTP 202:
{
"message_id": "018f5164-c1b2-7e2b-9d8a-2c60597f4c2f",
"accepted": true,
"delivered_live": false
}
accepted 表示中繼已將密文持久儲存。delivered_live 為 true 只表示中繼已把訊息交給即時遞送且沒有出錯;即使收件者沒有任何連線,它也是 true,所以不能用來判斷對方是否在線。兩者都不代表收件者已解密或顯示該訊息;收件匣裡的遞送回條才代表這件事(見「收件匣中的事件」)。
取得收件匣
GET /inbox/?limit=50
最多回傳 100 筆尚未確認的事件。同一個事件在逾時或重新啟動後可能再次出現。
{
"messages": [{
"sender_pubkey": "<hex>",
"discriminant": 11,
"payload_b64": "<ciphertext>",
"timestamp": 1780000000,
"msg_id": "018f5164-c1b2-7e2b-9d8a-2c60597f4c2f"
}],
"has_more": false
}
如果 has_more 為 true,請確認你已儲存的訊息,然後再次取得。
收件匣中的事件
收件匣存放中繼為你的身分排隊的所有項目,不只是新訊息。沒有 control_type 的項目是訊息:依「訊息信封」一節開啟 payload_b64。有 control_type 的項目是關於某則訊息的事件。解密任何內容前,請先檢查 control_type:回條沒有 payload_b64。
control_type | 意義 | msg_id |
|---|---|---|
message_receipt | 收件者的 App 已解密並儲存你傳送的訊息。 | 你那則訊息的 message_id |
message_read | 收件者已讀你的訊息。只有對方開啟已讀回條時才會送出。 | 你那則訊息的 message_id |
message_edit | 傳送者編輯了一則訊息(target_msg_id,與 msg_id 相同)。 | 被編輯訊息的 ID |
message_revoke | 傳送者收回了一則訊息。 | 被收回訊息的 ID |
message_reaction | 對 target_msg_id 新增或移除了表情回應。 | 表情回應的 ID |
編輯、收回與表情回應都有簽章(payload_sig),格式見 AeroNyx 聊天中繼用戶端整合。確認一個 ID 會移除佇列中所有帶有該 ID 的項目,因此請處理完整頁再確認。你不處理的事件類型也請確認,否則會再次回傳。
確認訊息
POST /acks/
{
"message_ids": ["018f5164-c1b2-7e2b-9d8a-2c60597f4c2f"]
}
只有在你驗證、解密訊息並寫入持久儲存空間之後,才可確認該訊息。未知的 ID 不會出現在回應的 acknowledged 清單中。
遞送模型
- 遞送為至少一次。請以
control_type加上msg_id去除重複:回條帶的是它所確認的那則訊息的 ID。 - 未遞送的密文會保留 72 小時。請勿將此服務當作永久的歷史紀錄。
- 傳送失敗時,請以完全相同的本文與全新的驗證 nonce 重試。
即時遞送
傳送
POST /messages/ 會把訊息交給收件者目前開著的每一條連線,同時把它留在佇列中,直到收件者的 App 確認為止。收件者的 App 在線時會立刻收到;離線的話,會在 App 下次連線時收到(72 小時內)。
透過此 API 傳送的訊息不會觸發推播通知。App 已關閉的收件者不會收到提醒,會在下次開啟 App 時看到訊息。
以輪詢接收
GET /inbox/ 會立即回傳,不論有沒有內容在等待。訊息要等你下一次呼叫才會拿到,所以你看到的延遲就是你的輪詢間隔。對話類用途請改用 WebSocket 接收。
即時接收
讓同一個身分連線到聊天中繼 WebSocket,不需要帳號或註冊。
- 開啟
wss://api.aeronyx.network/ws/relay/,並用同一把 Ed25519 金鑰登入,方法見 AeroNyx 聊天中繼用戶端整合 的「登入」一節。 - 登入後,中繼會先重送所有仍在佇列中的項目(
from_offline為true),之後每個新項目一到就立刻送出。訊息以relay_envelope訊框送達,欄位與收件匣中的訊息相同;回條、編輯、收回與表情回應則以type等於上表control_type的訊框送達。 - 儲存項目後,確認它:
{"type": "relay_offline_ack", "msg_id": "018f5164-c1b2-7e2b-9d8a-2c60597f4c2f"}
WebSocket 與 GET /inbox/ 讀的是同一個佇列:在其中一邊確認過的項目,另一邊也不會再出現,POST /acks/ 對兩者都有效。你可以繼續用 HTTPS 傳送,同時用 WebSocket 接收。
限制與錯誤
| 限制 | 數值 |
|---|---|
| 已封裝酬載 | 191 KiB(Base64 的 JSON 請求會維持在請求上限以下) |
| 請求本文 | 256 KiB |
| 收件匣分頁與確認批次 | 100 筆 |
| 離線保留 | 72 小時 |
| 狀態 | 意義 |
|---|---|
400 invalid_json | 本文不是 JSON 物件。 |
400 invalid_request | 本文未恰好包含這五個欄位、receiver_pubkey 不是 64 個小寫十六進位字元或等於你自己的位址、discriminant 超出 1-255、message_id 不是 UUID,或 payload_b64 不是有效的 Base64。 |
400 invalid_payload | 解碼後的酬載為空,或大於 191 KiB。 |
400 message_expired | sent_at 與伺服器時間相差超過 300 秒。 |
401 | 驗證缺失、已過期、無效或遭重放。請見排解 401。 |
403 | 收件者的政策拒絕了這個寄件者。 |
409 | 冪等性衝突,或相同的請求仍在處理中。 |
429 | 達到傳送方頻率限制:每個身分每秒 50 個請求,每個 UTC 日 100,000 個請求。請等待 Retry-After 標頭指定的時間。 |
503 | 持久遞送或驗證暫時無法使用。請以全新的 nonce 重試完全相同的本文。 |
本指南不涵蓋的內容
本指南只涵蓋透過 HTTPS 收發一對一訊息。其他功能請讓同一個身分連線到聊天中繼 WebSocket,並參閱 AeroNyx 聊天中繼用戶端整合:
- 群組訊息、在線狀態與輸入中提示。
- 加密附件,包括語音訊息與影片檔案。
- 語音與視訊通話。該文章列出了信令幀,但完整的通話整合指南(媒體權杖、邀請簽章與通話加密)尚未發布。
隱私與可見性
訊息內容為端對端加密:它在你這一端以由寄件者與收件者身分衍生的金鑰封裝,AeroNyx 從不持有該金鑰,而 API 只儲存並轉送密文。為了遞送訊息,服務可以看到寄件者與收件者的公鑰、訊息時間、大小、遞送狀態,以及每個請求的 IP 位址。HTTPS 無法對服務隱藏這些中繼資料。
訊息信封
AeroNyx App 使用者應該會看到的訊息,使用 discriminant: 11,並在 payload_b64 中放入端對端封裝的信封。中繼從不會看到金鑰或明文。所有整數皆為小端序。
1. 共用金鑰
雙方都從各自的 Ed25519 聊天身分衍生出相同的 32 位元組金鑰:
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)
使用 libsodium 時,這是 crypto_sign_ed25519_sk_to_curve25519、crypto_sign_ed25519_pk_to_curve25519 與 crypto_scalarmult,之後再接 HKDF。寄件者一律使用 HKDF 金鑰。非常舊的 App 版本直接以 raw 傳送,因此若 HKDF 金鑰解密失敗,接收者可以嘗試 raw。
2. 明文
文字訊息就是其 UTF-8 文字。來自 App、帶有附件、回覆或轉寄的訊息,是含有 "type": "aeronyx_message" 與 text 欄位的 JSON 物件;如果你不支援其餘部分,請顯示 text。
3. 加密
使用 XChaCha20-Poly1305,搭配全新的隨機 24 位元組 nonce,且不使用附加資料。下方的 ciphertext 是包含 16 位元組標籤的密碼輸出。
4. 簽章
寄件者以 Ed25519 簽署這 121 個位元組:
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. 信封
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 是這些位元組的標準 Base64。請注意,content_type 在簽署資料中是 1 位元組,但在信封中是 4 位元組。
接收
解析信封,檢查 receiver_pubkey 是你的身分,用信封的 sender_pubkey 驗證簽章,然後用由該寄件者衍生的金鑰解密。任何一個步驟失敗就丟棄該訊息。App 會丟棄 timestamp 與其時鐘相差超過 300 秒的即時訊息。
參考實作(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"),
}
黃金向量
固定的金鑰、nonce、ID 與時間,僅供測試。請勿使用這些金鑰。
{
"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))) 必須產生與此完全相同的 payload_b64,而 open_(bob, payload_b64) 必須回傳明文。此向量已對照 AeroNyx App 本身的金鑰協商、簽署器與密碼,以及節點的嚴格信封解碼器驗證過。
常見問題
什麼是 AeroNyx 聊天 API?
它是具版本的 HTTPS API,可讓伺服器、機器人或應用程式與 AeroNyx 使用者交換端對端加密的一對一訊息。它有四個端點:capabilities、傳送、收件匣與確認。它只承載已封裝的信封;內容在你這一端加密。
使用 API 需要 AeroNyx 帳號嗎?
不需要。Ed25519 金鑰對就是身分。在本機產生它,並用它為每個請求簽章;公鑰就是你的聊天位址。
我應該簽署 /messages/ 還是完整路徑?
簽署實際送出的完整路徑,從 /api/relay/developer/v1 開始,若有查詢字串則一併包含。只簽署 /messages/ 會產生 401。本頁的驗證測試向量列出了確切的輸入。
為什麼我會收到 401?
所有驗證失敗都會回傳相同的 401。常見原因有:時鐘誤差超過 60 秒、重複使用 nonce、簽署的路徑或本文與實際送出的不同,或簽署資料中的 HTTP 方法為小寫。請逐項檢查排解 401 下的清單,並將你的摘要與測試向量比對。
為什麼 API 回傳 202,但訊息從未出現在 AeroNyx App 中?
中繼會接受任何有效的 Base64 酬載,但 App 只會顯示它能驗證並解密的已封裝信封。請將你的 payload_b64 與本頁的黃金向量比對,確認信封的 receiver_pubkey 是收件者,並讓信封的時間戳與目前時間相差在 300 秒以內。傳送以 Base64 編碼的純文字會被接受,但永遠不會顯示。
為什麼 API 回傳 invalid_request?
最常見的原因是傳送給你自己的公鑰、本文未恰好包含文件所列的五個欄位、message_id 不是 UUID,或 discriminant 超出 1-255。請見限制與錯誤下的錯誤表。
AeroNyx 能讀取透過此 API 傳送的訊息嗎?
不能。信封使用由寄件者與收件者身分衍生的金鑰加密,AeroNyx 從不持有該金鑰。服務可以看到遞送中繼資料,例如公鑰、時間、大小與 IP 位址。
未遞送的訊息會保留多久?
最多 72 小時。只有在你解密並儲存每則訊息之後才確認它;未確認的訊息可能會再次遞送。
我可以使用哪些程式語言?
任何支援 Ed25519、X25519、HKDF-SHA256 與 XChaCha20-Poly1305 的語言,其中包括大多數語言的 libsodium 繫結。本頁包含完整的 Python 參考實作與可執行的快速開始。
AeroNyx 聊天 API 是即時的嗎?
傳送是即時的:收件者的 App 在線時會立刻收到。透過 HTTPS 接收是輪詢,要等你下次呼叫 GET /inbox/ 才會看到新訊息。若要即時接收,請讓同一個身分連線到聊天中繼 WebSocket(wss://api.aeronyx.network/ws/relay/),它讀的是同一個佇列。
我傳的訊息會讓 AeroNyx 使用者收到通知嗎?
目前不會。透過此 API 傳送的訊息不會觸發推播通知,所以 App 已關閉的使用者會在下次開啟 App 時才看到;App 在線的使用者則會立刻看到。
<!-- faq:end -->