{
    "name": "DVeProto",
    "version": 1,
    "canonical_url": "https://api.f-chat.ru/DVeProto",
    "description": "Application-layer framing: X25519 ECDH, HKDF-SHA256, AES-256-GCM. Transport-agnostic: the same JSON messages can be sent over WSS text frames, HTTPS request/response bodies, SSE, WebRTC data channel, message queues, etc.",
    "primitives": {
        "ecdh": "X25519",
        "hkdf": "HKDF-SHA256",
        "hkdf_salt": "",
        "hkdf_info_client_to_server": "DVeProto-v1/c2s",
        "hkdf_info_server_to_client": "DVeProto-v1/s2c",
        "aead": "AES-256-GCM",
        "nonce_length_bytes": 12
    },
    "transport": {
        "principle": "Each logical message is one UTF-8 JSON object on the wire (compact JSON). The channel only needs to preserve message boundaries or length; TLS is recommended for confidentiality and integrity of the channel itself.",
        "examples": {
            "websocket_text": "One JSON string per text frame after the WebSocket upgrade.",
            "https": "Each step can be POST with Content-Type: application/json body, or GET returning JSON; long-polling responses carry one JSON object per poll.",
            "ndjson": "One JSON object per line over a byte stream.",
            "other": "Any mechanism that delivers the handshake and dve frames as distinct JSON payloads."
        }
    },
    "handshake": {
        "order": {
            "server_sends": {
                "type": "dve_hello",
                "fields": [
                    "proto",
                    "v",
                    "server_pk"
                ],
                "server_pk_encoding": "base64",
                "server_pk_bytes": 32
            },
            "client_sends": {
                "type": "dve_client_ack",
                "fields": [
                    "proto",
                    "v",
                    "client_pk"
                ],
                "client_pk_encoding": "base64",
                "client_pk_bytes": 32
            }
        },
        "note": "Server uses a fresh X25519 key pair per logical session (e.g. per connection or per HTTP session, depending on binding). Shared secret = X25519(server_private, client_public)."
    },
    "data_frame": {
        "type": "dve",
        "fields": {
            "proto": "DVeProto",
            "v": 1,
            "n": "12-byte nonce, base64",
            "c": "AES-GCM ciphertext (nonce not included in c), base64"
        },
        "plaintext": "UTF-8 JSON object; compact serialization preferred.",
        "direction": {
            "client_to_server": "encrypt with HKDF-derived c2s key",
            "server_to_client": "encrypt with HKDF-derived s2c key"
        }
    },
    "reference_downloads": {
        "python3": "https://www.f-chat.ru/developers/dveproto/reference.py",
        "browser_esm": "https://www.f-chat.ru/developers/dveproto/reference-client.js"
    },
    "developer_experience": {
        "goal": "Same crypto as the wire format, but clearer steps and optional conventions so integrations look like a small SDK rather than raw frame assembly.",
        "client_python": [
            "DVeClientSession.from_server_hello_text(hello_json) -> (session, client_ack_json)",
            "session.pack_outgoing(app_obj) / unpack_incoming(dve_frame_json)",
            "developer_server_session_from_ack(server_private, client_ack_text) -> DVeSession (server encrypt/decrypt)"
        ],
        "client_javascript": [
            "dveDeveloperHandshake(hello) -> { session, clientAckJson }",
            "session.encryptOutgoing / decryptIncoming unchanged",
            "dveAppMessage(op, extra?, seq?) and DVeSeq for optional dve_op / dve_seq in plaintext JSON"
        ],
        "app_message_convention": {
            "dve_op": "string, logical RPC or message name agreed between client and server",
            "dve_seq": "optional int, monotonic per session for ordering (use DVeSeq / DVeSeq in Python)",
            "note": "Plaintext JSON inside AES-GCM; fields are not authenticated separately from the AEAD payload."
        }
    },
    "documentation": {
        "human": "https://www.f-chat.ru/developers/dveproto/"
    },
    "exchange_api": {
        "url": "https://api.f-chat.ru/dve_exchange.php",
        "openapi": "https://api.f-chat.ru/dve_exchange.openapi.yaml",
        "auth": "Same session cookies as feed (secure_session + auth_token).",
        "routing_modes": {
            "e2e": "Device encrypts → server stores opaque payload → recipient decrypts. Server never sees plaintext.",
            "gateway": "Device encrypts with server RSA public key (hybrid RSA-OAEP + AES-256-GCM) → server decrypts and stores plaintext for recipient. Use only if you accept server-readable content."
        },
        "gateway_public_key": "GET ?action=gateway_public_key — public_key_pem + public_keys[{kid, public_key_pem}]; optional kid in gateway_envelope; 503 if not configured.",
        "capabilities": "GET ?action=capabilities — limits (env FLOWCHAT_DVE_EX_*), retention_days, gateway key_count, privacy_note.",
        "metrics": "GET ?action=metrics&key=<FLOWCHAT_DVE_EX_METRICS_SECRET> — aggregate counters.",
        "limits_env": {
            "FLOWCHAT_DVE_EX_USER_HOUR": "Max sends per sender per hour (default 120).",
            "FLOWCHAT_DVE_EX_PAIR_HOUR": "Max sends per sender→recipient pair per hour (default 60).",
            "FLOWCHAT_DVE_EX_MAX_UNREAD": "Block sends to recipient if unread inbox count reaches this (default 5000).",
            "FLOWCHAT_DVE_EX_RETENTION_DAYS": "If >0, old messages may be deleted (probabilistic; 0=off).",
            "FLOWCHAT_DVE_EX_METRICS_SECRET": "If set, enables GET metrics."
        },
        "idempotency": "POST send optional client_msg_id (1–128 chars [a-zA-Z0-9._:@-]); duplicate returns same id with idempotent:true.",
        "send_e2e": "POST JSON { action: \"send\", routing_mode: \"e2e\" (default), to_user_hash, payload, client_msg_id?: \"...\" }",
        "send_gateway": "POST JSON { action: \"send\", routing_mode: \"gateway\", to_user_hash, gateway_envelope: { ek, iv, ct [, kid] } }",
        "inbox_outbox": "routing_mode, optional client_msg_id, payload (opaque e2e / plaintext gateway).",
        "inbox": "GET ?action=inbox&after_id=0&limit=50&unread=1 — messages where you are recipient",
        "outbox": "GET ?action=outbox&after_id=0",
        "ack_read": "POST JSON { action: \"ack_read\", ids: [1,2,3] }",
        "privacy": "Do not log e2e payloads in debug output; gateway mode implies server can read plaintext after decrypt."
    },
    "machine_readable_spec_url": "https://api.f-chat.ru/DVeProto",
    "legacy_spec_url": "https://api.f-chat.ru/dveproto_spec.php"
}