Status
Endpoint
GET /healthDB
—
Timestamp
—
Raw JSON
Waiting…
Canonical rules (wire + DB)
- Convro number: zawsze
+99+ dokładnie 6 cyfr (DB check:^\+99[0-9]{6}$). - deviceId: zawsze 16 hex (np.
2baacb2f815cd54e). - sessionId: zawsze 8 hex (np.
9f12a0bc). - keyId (prekeys): zawsze 16 hex (signed prekey i OTP).
- No plaintext: DM/Group/Channel messages = tylko
ciphertext+authTag. - DM authTag: 16 bytes (DB:
dm_messages.auth_tag VARBINARY(16)). - JWT access token: payload niesie
userId,deviceId,convroNumber,username. - Refresh token: rotacja (stary jest revoke), server trzyma
token_hash = sha256(secret). - ACL: DM session/messages tylko dla device należących do sesji (FK w DB).
- Receipts: tylko recipient device może oznaczyć
DELIVERED/READ(server waliduje). - Base64: prekeys używają standard base64; handshake offer toleruje base64url (bez padding) tam gdzie trzeba.
Socket.IO (realtime)
WS endpoint: same host/port, path: /socket.io
Auth: accessToken w handshake:
- socket.handshake.auth.token = "<JWT>"
- albo query ?token=<JWT>
Room: device:<deviceId>
Minimal events:
- client -> "ping"
- server -> "pong"
Server log: "[WS] Connected userId=... deviceId=..."
Endpoints (aktywnie zarejestrowane)
Health
GET /healthAuth
POST /v1/auth/registerAuth
POST /v1/auth/loginAuth
POST /v1/auth/refreshAuth
POST /v1/auth/logoutAuth
GET /v1/auth/meDevices
POST /v1/auth/devices/registerDevices
GET /v1/auth/devicesUsers
GET /v1/users/resolvePrekeys
POST /v1/prekeys/uploadPrekeys
GET /v1/prekeys/bundleDM Sessions
POST /v1/dm/sessions/openDM Sessions
GET /v1/dm/sessions/incomingDM Sessions
POST /v1/dm/sessions/acceptDM
POST /v1/dm/messages/sendDM
GET /v1/dm/messages/historyDM
GET /v1/dm/messages/conversationsReceipts
POST /v1/dm/messages/receipts
Wszystkie endpointy pod /v1/* wymagają nagłówka:
Authorization: Bearer <accessToken>,
chyba że opis mówi inaczej.
Auth
POST /v1/auth/register (public)
Body JSON:
{
"password": "min 8 chars",
"username": "optional, 3..64, [a-zA-Z0-9_.]",
"fullName": "optional, max 128",
"deviceId": "optional 16-hex; jeśli brak -> server generuje",
"platform": "ios|android|desktop|web|unknown",
"deviceLabel": "optional, max 128"
}
201 Response:
{
"userId": 1,
"convroNumber": "+99xxxxxx",
"username": "sigmabo" | null,
"fullName": "..." | null,
"deviceId": "16hex",
"platform": "ios",
"accessToken": "<JWT>",
"refreshToken": "rt_<jti>_<secretB64url>"
}
Errors:
- 400 PASSWORD_TOO_SHORT / INVALID_USERNAME / INVALID_DEVICE_ID
- 409 USERNAME_TAKEN / DEVICE_ID_TAKEN
- 500 FAILED_TO_ALLOCATE_CONVRO_NUMBER / INTERNAL_ERROR
POST /v1/auth/login (public)
Body JSON:
{
"password": "...",
"username": "..." | null,
"convroNumber": "+99xxxxxx" | null,
"deviceId": "optional 16-hex",
"platform": "ios|android|desktop|web|unknown",
"deviceLabel": "optional"
}
200 Response:
{ same shape as /register }
Errors:
- 400 MISSING_LOGIN_IDENTIFIER / INVALID_DEVICE_ID
- 401 INVALID_CREDENTIALS
- 403 DEVICE_OWNED_BY_OTHER_USER
POST /v1/auth/refresh (public)
Body JSON:
{ "refreshToken": "rt_..." }
200 Response:
{
"userId": 1,
"deviceId": "16hex",
"convroNumber": "+99xxxxxx",
"username": "..." | null,
"accessToken": "<JWT>",
"refreshToken": "rt_..." // nowy, stary revoke
}
Errors:
- 400 INVALID_REFRESH_TOKEN
- 401 REFRESH_NOT_FOUND / REFRESH_REVOKED / REFRESH_EXPIRED / REFRESH_INVALID
POST /v1/auth/logout (auth required)
Header:
Authorization: Bearer <accessToken>
Body JSON (jedna z opcji):
1) revoke single:
{ "refreshToken": "rt_..." }
2) revoke all for device (default gdy brak refreshToken):
{ "allForDevice": true }
3) revoke all for user (wszystkie device):
{ "allForUser": true }
200 Response:
{ "ok": true, "scope": "single|device|user" }
GET /v1/auth/me (auth required)
200 Response:
{
"userId": 1,
"deviceId": "16hex",
"convroNumber": "+99xxxxxx",
"username": "..." | null
}
Users (resolve)
GET /v1/users/resolve (auth required)
Cel: start DM po +99 / username (UI: wpisujesz numer -> dostajesz peerUserId).
Header:
Authorization: Bearer <accessToken>
Query (jedno z):
- ?convroNumber=+99xxxxxx
- ?username=sigmabo
200 Response (minimal):
{
"userId": 123,
"convroNumber": "+99xxxxxx",
"username": "..." | null,
"fullName": "..." | null
}
Errors:
- 400 INVALID_QUERY
- 404 USER_NOT_FOUND
Tip: do handshaku nie musisz znać deviceId peera — /v1/prekeys/bundle bez device_id
wybierze pierwsze aktywne urządzenie użytkownika.
Prekeys
POST /v1/prekeys/upload (auth required)
Header:
Authorization: Bearer <accessToken>
Body JSON:
{
"identity": { "publicKeyEd25519": "<base64 32B>", "fingerprint": "optional" },
"signedPrekey": {
"keyId": "<16hex>",
"publicKeyX25519": "<base64 32B>",
"signatureEd25519": "<base64 64B>"
},
"oneTimePrekeys": [
{ "keyId": "<16hex>", "publicKeyX25519": "<base64 32B>" }
]
}
200 Response:
{ "ok": true, "deviceId": "16hex", "storedOneTimePrekeys": 0..N }
Notes:
- identity i signedPrekey są wymagane
- OTP batch opcjonalny (ale zalecany)
GET /v1/prekeys/bundle?user_id=...&device_id=... (auth required)
Header:
Authorization: Bearer <accessToken>
Query:
- user_id (required) => peer userId
- device_id (optional) => 16hex peer deviceId
200 Response:
{
"responderDeviceId": "16hex",
"identityPublicKeyEd25519": "<base64 32B>",
"signedPrekeyId": "<16hex>",
"signedPrekeyPublicKeyX25519": "<base64 32B>",
"signedPrekeySignature": "<base64 64B>",
"oneTimePrekeyId": "<16hex>" | null,
"oneTimePrekeyPublicKeyX25519": "<base64 32B>" | null
}
Canonical:
- OTP jest konsumowany tutaj (atomowo), nie w DM session.
DM Sessions (handshake)
POST /v1/dm/sessions/open (auth required)
Header:
Authorization: Bearer <accessToken>
Body JSON:
{
"peerUserId": 123,
"handshakeOffer": {
"version": 1,
"initiatorDeviceId": "16hex (musi == deviceId z JWT)",
"responderDeviceId": "16hex",
"sessionId": "8hex",
"ephemeralPublicKeyX25519": "<base64url/base64 32B>",
"usedSignedPrekeyPublicKeyX25519": "<base64url/base64 32B>",
"usedOneTimePrekeyId": "<16hex>" | null,
"...": "inne pola offer zależne od C6P v1"
}
}
201 Response:
{ "ok": true, "sessionDbId": 1, "sessionId": "8hex", "responderUserId": 123, "responderDeviceId": "16hex", "state": "PENDING" }
Errors:
- 409 SESSION_ID_EXISTS
- 404 INITIATOR_DEVICE_NOT_FOUND / RESPONDER_DEVICE_NOT_FOUND
- 413 HANDSHAKE_OFFER_TOO_LARGE
GET /v1/dm/sessions/incoming (auth required)
Header:
Authorization: Bearer <accessToken>
200 Response:
{
"sessions": [
{
"sessionDbId": 1,
"sessionId": "8hex",
"initiatorUserId": 55,
"initiatorDeviceId": "16hex",
"handshakeOffer": { ... },
"createdAt": "ISO"
}
]
}
POST /v1/dm/sessions/accept (auth required)
Header:
Authorization: Bearer <accessToken>
Body JSON:
{ "sessionDbId": 1 }
200 Response:
{ "ok": true, "sessionDbId": 1, "sessionId": "8hex", "state": "ACTIVE" }
Notes:
- akceptuje tylko responder device z sesji
DM Messages
POST /v1/dm/messages/send (auth required)
Header:
Authorization: Bearer <accessToken>
Body JSON (canonical minimum):
{
"sessionId": "8hex",
"recipientDeviceId": "16hex",
"clientMessageId": "optional string up to 64",
"clientTimestamp": "optional ISO",
"ciphertext": "<base64>",
"authTag": "<base64 (16B)>"
}
201/200 Response (typowo):
{
"ok": true,
"messageId": 123,
"serverTimestamp": "ISO",
"deliveryState": "PENDING"
}
Realtime:
- server emituje do room: device:<recipientDeviceId> (payload z message/meta)
GET /v1/dm/messages/history (auth required)
Header:
Authorization: Bearer <accessToken>
Query (typowo):
- ?sessionId=8hex
- ?beforeId=123 (optional) / ?limit=50 (optional)
200 Response:
{ "messages": [ ... ] }
GET /v1/dm/messages/conversations (auth required)
Header:
Authorization: Bearer <accessToken>
200 Response:
{ "conversations": [ ... ] }
POST /v1/dm/messages/receipts (auth required)
Header:
Authorization: Bearer <accessToken>
Body JSON:
{
"messageId": 123,
"status": "DELIVERED" | "READ"
}
200 Response:
{ "ok": true }
Notes:
- tylko recipient device może wystawić receipt
- DB: message_receipts ma uniq(message_id, device_id)
Flow (DM po numerze +99)
- Register/Login → dostajesz
accessToken+refreshToken+deviceId+convroNumber. - Upload prekeys →
POST /v1/prekeys/upload(identity + signed prekey + OTP batch). - Resolve peer →
GET /v1/users/resolve?convroNumber=+99xxxxxx→ dostajeszpeerUserId. - Get prekey bundle →
GET /v1/prekeys/bundle?user_id=<peerUserId>(server wybiera aktywny device). - Handshake offer → generujesz C6P offer →
POST /v1/dm/sessions/open. - Responder →
GET /v1/dm/sessions/incoming→POST /v1/dm/sessions/accept→ sesjaACTIVE. - Messaging →
POST /v1/dm/messages/send+ realtime przez Socket.IO roomdevice:<deviceId>.