Guia de integração da API de chat da AeroNyx

14 min de leitura

Envie e receba mensagens da AeroNyx com criptografia de ponta a ponta por HTTPS: assinatura de requisições, um início rápido executável, o formato do envelope selado, códigos de erro e vetores de teste.

A API de chat da AeroNyx permite que um servidor, um bot ou um aplicativo envie e receba mensagens individuais com criptografia de ponta a ponta de usuários da AeroNyx por meio de HTTPS comum. Ela não usa a AeroNyx Privacy Network, a VPN, o roteamento onion nem a caixa de correio anônima.

O envio é imediato: se o App do destinatário estiver conectado, a mensagem chega na hora. O recebimento por esta API funciona por polling: GET /inbox/ retorna o que estiver aguardando e nunca mantém a requisição aberta. Para receber em tempo real, conecte a mesma identidade ao WebSocket do Chat Relay (wss://api.aeronyx.network/ws/relay/). Veja a seção "Entrega em tempo real" abaixo.

URL base: https://api.aeronyx.network/api/relay/developer/v1

Como funciona

  1. Crie uma identidade. Um par de chaves Ed25519 é a sua identidade de chat. A chave pública de 32 bytes, escrita como 64 caracteres hexadecimais em minúsculas, é o seu endereço de chat. Não é necessária nenhuma conta da AeroNyx.
  2. Assine cada requisição. Cada chamada leva um cabeçalho Authorization assinado com a sua chave (Autenticação).
  3. Sele cada mensagem. Criptografe e assine a mensagem do seu lado e envie apenas o envelope resultante (Envelope da mensagem).
  4. Envie com POST /messages/.
  5. Receba com GET /inbox/, descriptografe e armazene cada mensagem e, em seguida, confirme-a com POST /acks/.
EndpointAutenticaçãoFinalidade
GET /capabilities/nenhumaLimites do serviço e recursos compatíveis
POST /messages/obrigatóriaEnviar uma mensagem selada
GET /inbox/obrigatóriaBuscar mensagens não confirmadas
POST /acks/obrigatóriaConfirmar as mensagens que você armazenou

Início rápido

Este script envia uma mensagem de uma identidade nova para outra, lê a mensagem de volta, a descriptografa e a confirma. Salve a implementação de referência abaixo como aeronyx_chat.py, instale as dependências com pip install pynacl requests e execute o script.

python
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())

Autenticação

Todos os endpoints, exceto GET /capabilities/, exigem este cabeçalho:

text
Authorization: AeroNyx-Chat-V1 <public-key-hex>:<unix-seconds>:<nonce-hex>:<signature-hex>
  • public-key-hex é o seu endereço de chat de 64 caracteres.
  • unix-seconds deve estar a até 60 segundos do relógio do servidor.
  • nonce-hex são 16 bytes aleatórios escritos como 32 caracteres hexadecimais em minúsculas. Cada nonce só pode ser usado uma vez.
  • signature-hex é uma assinatura Ed25519, escrita como 128 caracteres hexadecimais, sobre este digest de 32 bytes:
text
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 é o caminho completo da requisição como enviado, incluindo o prefixo /api/relay/developer/v1 e qualquer query string. Por exemplo:

RequisiçãoPATH_WITH_QUERY a assinar
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 são os bytes do corpo da requisição exatamente como enviados. Por isso, os espaços em branco e a ordem das chaves do JSON importam: serialize uma única vez, assine esses bytes e envie esses mesmos bytes. Uma requisição sem corpo assina o SHA-256 da string vazia.

Vetor de teste de autenticação

Use-o para verificar o seu código de assinatura antes de chamar a API. A chave serve apenas para testes; nunca a use em mensagens reais.

json
{
  "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"
}

As assinaturas Ed25519 são determinísticas, portanto as mesmas entradas devem gerar exatamente este signature_hex. Se o seu body_sha256_hex for diferente, os bytes do seu corpo são diferentes.

Solução de problemas do 401

A API responde a toda falha de autenticação com o mesmo 401, portanto verifique os itens abaixo na ordem:

  1. O relógio: unix-seconds está a até 60 segundos do horário real.
  2. O nonce é novo a cada requisição, tem 16 bytes e está escrito como 32 caracteres hexadecimais em minúsculas.
  3. O caminho assinado inclui /api/relay/developer/v1 e a query string.
  4. O método assinado está em maiúsculas e corresponde à requisição.
  5. Os bytes do corpo assinado são os bytes que você envia. Não serialize o JSON novamente depois de assinar.
  6. O seu digest e a sua assinatura coincidem com o vetor de teste acima.

Endpoints

Informações do serviço

GET /capabilities/ não precisa de autenticação e retorna os limites atuais e os recursos compatíveis.

Enviar uma mensagem

POST /messages/

json
{
  "receiver_pubkey": "<64 lowercase hex characters>",
  "message_id": "018f5164-c1b2-7e2b-9d8a-2c60597f4c2f",
  "discriminant": 11,
  "sent_at": 1780000000,
  "payload_b64": "<sealed envelope, Base64>"
}

Todos os cinco campos são obrigatórios e nenhum outro é permitido.

  • receiver_pubkey é o endereço de chat do destinatário. Ele deve ser diferente do seu próprio endereço.
  • message_id é um UUID. Use um novo UUID aleatório para cada mensagem e guarde o corpo exato para novas tentativas. Enviar o mesmo message_id com campos diferentes retorna 409 idempotency_conflict.
  • discriminant é 11 para uma mensagem individual que um usuário do AeroNyx App deve ver.
  • sent_at é em segundos Unix e deve estar a até 300 segundos do horário do servidor.
  • payload_b64 é o envelope selado descrito em Envelope da mensagem.

O relay verifica apenas se payload_b64 é um Base64 válido de 1 byte a 191 KiB. Ele não consegue distinguir um envelope de outros dados, portanto um payload que não seja um envelope válido é aceito aqui e descartado silenciosamente pelo App do destinatário. Teste o seu envelope com o vetor de referência.

Uma requisição bem-sucedida retorna HTTP 202:

json
{
  "message_id": "018f5164-c1b2-7e2b-9d8a-2c60597f4c2f",
  "accepted": true,
  "delivered_live": false
}

accepted significa que o relay armazenou o texto cifrado de forma durável. delivered_live é true quando o relay passou a mensagem para a entrega ao vivo sem erro. Ele é true mesmo quando o destinatário não tem nenhuma conexão aberta, então não indica se alguém está online. Nenhum dos dois campos significa que o destinatário decifrou ou exibiu a mensagem; quem indica isso é um recibo de entrega na sua caixa de entrada (veja "Eventos na caixa de entrada").

Buscar a caixa de entrada

GET /inbox/?limit=50

Retorna até 100 eventos não confirmados. O mesmo evento pode aparecer novamente após um timeout ou uma reinicialização.

json
{
  "messages": [{
    "sender_pubkey": "<hex>",
    "discriminant": 11,
    "payload_b64": "<ciphertext>",
    "timestamp": 1780000000,
    "msg_id": "018f5164-c1b2-7e2b-9d8a-2c60597f4c2f"
  }],
  "has_more": false
}

Se has_more for true, confirme o que você armazenou e busque novamente.

Eventos na caixa de entrada

A caixa de entrada guarda tudo o que o relay enfileirou para a sua identidade, não só mensagens novas. Um item sem control_type é uma mensagem: abra payload_b64 como descrito em "Envelope da mensagem". Um item com control_type é um evento sobre uma mensagem. Verifique control_type antes de decifrar qualquer coisa: recibos não têm payload_b64.

control_typeSignificadomsg_id
message_receiptO App do destinatário decifrou e armazenou uma mensagem que você enviou.O message_id da sua mensagem
message_readO destinatário leu a sua mensagem. Enviado só quando ele tem recibos de leitura ativados.O message_id da sua mensagem
message_editO remetente editou uma mensagem (target_msg_id, o mesmo ID).O ID da mensagem editada
message_revokeO remetente retirou uma mensagem.O ID da mensagem retirada
message_reactionUma reação a target_msg_id foi adicionada ou removida.O ID da reação

Edições, retiradas e reações são assinadas (payload_sig); os formatos estão descritos em Integração de clientes com o AeroNyx Chat Relay. Confirmar um ID remove todos os itens na fila que o carregam, então processe a página inteira antes de confirmar. Confirme também os tipos de evento que você não trata; caso contrário, eles voltam.

Confirmar mensagens

POST /acks/

json
{
  "message_ids": ["018f5164-c1b2-7e2b-9d8a-2c60597f4c2f"]
}

Confirme uma mensagem somente depois de validá-la, descriptografá-la e gravá-la em armazenamento durável. IDs desconhecidos ficam de fora da lista acknowledged na resposta.

Modelo de entrega

  • A entrega é pelo menos uma vez. Elimine duplicatas por control_type junto com msg_id: um recibo carrega o ID da mensagem que ele confirma.
  • O texto cifrado não entregue é mantido por 72 horas. Não trate o serviço como um histórico permanente.
  • Repita um envio que falhou com exatamente o mesmo corpo e um nonce de autenticação novo.

Entrega em tempo real

Envio

POST /messages/ entrega a sua mensagem a todas as conexões que o destinatário tem abertas e, ao mesmo tempo, a mantém na fila até que o App do destinatário a confirme. Um destinatário com o App conectado recebe na hora; um que está offline recebe na próxima vez que o App se conectar, dentro de 72 horas.

Mensagens enviadas por esta API não disparam notificação push. Um destinatário com o App fechado não é avisado e verá a mensagem na próxima vez que abrir o App.

Recebimento por polling

GET /inbox/ retorna na hora, haja ou não algo aguardando. Uma mensagem só chega a você na próxima chamada, então o atraso que você vê é o seu intervalo de polling. Para conversas, receba pelo WebSocket.

Recebimento em tempo real

Conecte a mesma identidade ao WebSocket do Chat Relay. Não é preciso conta nem cadastro.

  1. Abra wss://api.aeronyx.network/ws/relay/ e faça login com a mesma chave Ed25519, como descrito na parte de login de Integração de clientes com o AeroNyx Chat Relay.
  2. Após o login, o relay primeiro reenvia tudo o que ainda está na fila para você (from_offline é true) e depois envia cada item novo assim que ele chega. Mensagens chegam como frames relay_envelope com os mesmos campos de uma mensagem da caixa de entrada; recibos, edições, retiradas e reações chegam como frames cujo type é o control_type da tabela acima.
  3. Depois de armazenar um item, confirme-o:
json
{"type": "relay_offline_ack", "msg_id": "018f5164-c1b2-7e2b-9d8a-2c60597f4c2f"}

O WebSocket e GET /inbox/ leem a mesma fila: um item confirmado em um some do outro, e POST /acks/ vale para os dois. Você pode continuar enviando por HTTPS enquanto recebe pelo WebSocket.

Limites e erros

LimiteValor
Payload selado191 KiB (o JSON da requisição em Base64 fica abaixo do limite da requisição)
Corpo da requisição256 KiB
Página da caixa de entrada e lote de confirmações100 itens
Retenção offline72 horas
StatusSignificado
400 invalid_jsonO corpo não é um objeto JSON.
400 invalid_requestO corpo não contém exatamente os cinco campos, receiver_pubkey não tem 64 caracteres hexadecimais em minúsculas ou é igual ao seu próprio endereço, discriminant está fora do intervalo de 1 a 255, message_id não é um UUID, ou payload_b64 não é um Base64 válido.
400 invalid_payloadO payload decodificado está vazio ou é maior que 191 KiB.
400 message_expiredsent_at difere do horário do servidor em mais de 300 segundos.
401A autenticação está ausente, expirada, inválida ou foi reutilizada (replay). Consulte Solução de problemas do 401.
403A política do destinatário rejeitou este remetente.
409Conflito de idempotência, ou uma requisição idêntica ainda está em andamento.
429O limite de taxa do remetente foi atingido: 50 requisições por segundo e 100.000 por dia UTC para cada identidade. Aguarde o tempo indicado no cabeçalho Retry-After.
503A entrega durável ou a autenticação está temporariamente indisponível. Repita o corpo exato com um nonce novo.

O que este guia não cobre

Este guia cobre mensagens individuais por HTTPS. Para todo o resto, conecte a mesma identidade ao WebSocket do relay de chat e consulte Integração de clientes com o AeroNyx Chat Relay:

  • Mensagens de grupo, presença e indicadores de digitação.
  • Anexos criptografados, incluindo mensagens de voz e arquivos de vídeo.
  • Chamadas de voz e vídeo. Os frames de sinalização estão listados nesse artigo, mas um guia completo de integração de chamadas (token de mídia, assinatura de convite e criptografia da chamada) ainda não foi publicado.

Privacidade e visibilidade

O conteúdo das mensagens tem criptografia de ponta a ponta: ele é selado do seu lado com uma chave derivada das identidades do remetente e do destinatário, que a AeroNyx nunca possui, e a API armazena e encaminha apenas o texto cifrado. Para entregar as mensagens, o serviço consegue ver as chaves públicas do remetente e do destinatário, o horário da mensagem, o tamanho, o estado de entrega e o endereço IP de cada requisição. O HTTPS não oculta esses metadados do serviço.

Envelope da mensagem

As mensagens que um usuário do AeroNyx App deve ver usam discriminant: 11 e um envelope selado de ponta a ponta em payload_b64. O relay nunca vê a chave nem o texto simples. Todos os inteiros são little-endian.

1. Chave compartilhada

Os dois lados derivam a mesma chave de 32 bytes a partir das suas identidades de chat Ed25519:

text
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)

Com a libsodium, isso corresponde a crypto_sign_ed25519_sk_to_curve25519, crypto_sign_ed25519_pk_to_curve25519 e crypto_scalarmult, seguidos de HKDF. Os remetentes sempre usam a chave HKDF. Versões muito antigas do App enviavam com raw diretamente, portanto um destinatário pode tentar raw se a chave HKDF falhar ao descriptografar.

2. Texto simples

Uma mensagem de texto é o seu próprio texto em UTF-8. As mensagens do App que contêm anexos, respostas ou encaminhamentos são um objeto JSON com "type": "aeronyx_message" e um campo text; exiba text se você não der suporte ao restante.

3. Criptografia

XChaCha20-Poly1305 com um nonce aleatório novo de 24 bytes e sem dados associados. ciphertext abaixo é a saída da cifra, incluindo a sua tag de 16 bytes.

4. Assinatura

O remetente assina estes 121 bytes com Ed25519:

text
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

text
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 é o Base64 padrão desses bytes. Observe que content_type tem 1 byte nos dados assinados, mas 4 bytes no envelope.

Recebimento

Analise o envelope, verifique se receiver_pubkey é a sua identidade, verifique a assinatura com o sender_pubkey do envelope e, em seguida, descriptografe com a chave derivada desse remetente. Descarte a mensagem se qualquer etapa falhar. O App descarta mensagens ao vivo cujo timestamp esteja a mais de 300 segundos do seu relógio.

Implementação de referência (Python)

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"),
    }

Vetor de referência

Chaves, nonce, ID e horário fixos, apenas para testes. Nunca use estas chaves.

json
{
  "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))) deve produzir exatamente este payload_b64, e open_(bob, payload_b64) deve retornar o texto simples. Este vetor foi verificado com o acordo de chaves, o assinador e a cifra do próprio AeroNyx App e com o decodificador estrito de envelopes do nó.

<!-- faq:start -->

Perguntas frequentes

O que é a API de chat da AeroNyx?

É uma API HTTPS versionada para trocar mensagens individuais com criptografia de ponta a ponta com usuários da AeroNyx a partir de um servidor, bot ou aplicativo. Ela tem quatro endpoints: capabilities, envio, caixa de entrada e confirmação. Ela transporta apenas envelopes selados; o conteúdo é criptografado do seu lado.

Preciso de uma conta da AeroNyx para usar a API?

Não. Um par de chaves Ed25519 é a identidade. Gere-o localmente e assine cada requisição com ele; a chave pública é o seu endereço de chat.

Eu assino /messages/ ou o caminho completo?

O caminho completo como enviado, começando com /api/relay/developer/v1, mais a query string, se houver. Assinar apenas /messages/ resulta em um 401. O vetor de teste de autenticação desta página mostra as entradas exatas.

Por que recebo 401?

Toda falha de autenticação retorna o mesmo 401. As causas habituais são um relógio com mais de 60 segundos de diferença, um nonce reutilizado, um caminho ou corpo assinado diferente do que foi enviado, ou um método HTTP em minúsculas nos dados assinados. Percorra a lista de verificação em Solução de problemas do 401 e compare o seu digest com o vetor de teste.

Por que a API retorna 202, mas a mensagem nunca aparece no AeroNyx App?

O relay aceita qualquer payload em Base64 válido, mas o App só exibe um envelope selado que consiga verificar e descriptografar. Confira o seu payload_b64 com o vetor de referência desta página, certifique-se de que o receiver_pubkey do envelope seja o destinatário e mantenha o timestamp do envelope a até 300 segundos do horário atual. Enviar texto simples codificado em Base64 é aceito, mas nunca é exibido.

Por que a API retorna invalid_request?

As causas mais comuns são enviar para a sua própria chave pública, um corpo que não contém exatamente os cinco campos documentados, um message_id que não é um UUID, ou um discriminant fora do intervalo de 1 a 255. Consulte a tabela de erros em Limites e erros.

A AeroNyx consegue ler as mensagens enviadas por esta API?

Não. Os envelopes são criptografados com uma chave derivada das identidades do remetente e do destinatário, que a AeroNyx nunca possui. O serviço consegue ver metadados de entrega, como chaves públicas, horários, tamanhos e endereços IP.

Por quanto tempo as mensagens não entregues são mantidas?

Por até 72 horas. Confirme cada mensagem somente depois de descriptografá-la e armazená-la; as mensagens não confirmadas podem ser entregues novamente.

Quais linguagens de programação posso usar?

Qualquer linguagem com Ed25519, X25519, HKDF-SHA256 e XChaCha20-Poly1305, o que inclui bindings da libsodium para a maioria das linguagens. Esta página inclui uma implementação de referência completa em Python e um início rápido executável.

A API de chat da AeroNyx é em tempo real?

O envio é: um destinatário com o App conectado recebe a sua mensagem na hora. O recebimento por HTTPS é por polling, então você vê mensagens novas na próxima chamada a GET /inbox/. Para receber em tempo real, conecte a mesma identidade ao WebSocket do Chat Relay em wss://api.aeronyx.network/ws/relay/; ele lê a mesma fila.

Um usuário da AeroNyx recebe notificação por uma mensagem que eu envio?

No momento, não. Mensagens enviadas por esta API não disparam notificação push, então um usuário com o App fechado verá a sua mensagem na próxima vez que abri-lo. Um usuário com o App conectado vê na hora.

<!-- faq:end -->